Skip to content

models

strands_teams.models

Base Pydantic models and generic result primitives for strands_teams.

This module hosts the framework-level data-transfer types shared by dispatch, hooks, display, and use-case layers. All models are domain-agnostic: they carry no codebase-analysis vocabulary. Concrete use cases subclass or compose these.

Copied and renamed from the pre-split code_context_agent package: - StrictModel / FrozenModel from models/base.py. - Bundle, GeneratedFile, PhaseTiming from models/output.py (verbatim). - AnalysisConfidence -> TeamConfidence and CalibratedJudgment (renamed).

EvidenceType module-attribute

EvidenceType = Literal[
    "file_line", "test", "command", "artifact", "url", "log"
]

Kinds of evidence a finding can cite (file:line, a test, a command exit, an artifact revision, a URL, or a log excerpt).

Severity module-attribute

Severity = Literal[
    "info", "low", "medium", "high", "critical"
]

Ordered severity levels for a finding.

Disposition module-attribute

Disposition = Literal[
    "open",
    "acknowledged",
    "remediated",
    "accepted_risk",
    "rejected",
    "superseded",
]

Lifecycle states a finding moves through as it is triaged and resolved.

NO_CURRENT_REVISION module-attribute

NO_CURRENT_REVISION = '∅'

Sentinel expected_current_revision meaning "this logical_name has no current revision yet".

A first promotion of a logical_name passes this sentinel as expected_current_revision (spec 005 AC-2-4). It is deliberately a value no real revision_id (a hex content hash) can collide with.

EpistemicStatus module-attribute

EpistemicStatus = Literal[
    "observed",
    "inferred",
    "hypothesized",
    "disputed",
    "accepted",
    "rejected",
    "superseded",
]

The epistemic standing of a TeamEvent's claim (AC-1-2).

  • observed — a directly recorded fact (e.g. a dispatch completed).
  • inferred — derived from other events without direct observation.
  • hypothesized — a tentative claim awaiting confirmation.
  • disputed — contradicted by another event/actor.
  • accepted / rejected — an adjudicated outcome.
  • superseded — replaced by a later event (retained in the log, never deleted).

HumanDecisionStatus module-attribute

HumanDecisionStatus = Literal[
    "pending", "resolved", "rejected"
]

Lifecycle of a :class:HumanDecision gate.

  • pending — awaiting a human; the runner suspends the transition (AC-4-3).
  • resolved — approved by a human; the runner resumes and re-authorizes (AC-4-4).
  • rejected — declined by a human; the runner abandons the transition (AC-4-6).

GateName module-attribute

GateName = Literal[
    "capability",
    "separation_of_duties",
    "required_approvals",
    "open_blocking_finding",
    "human_required",
]

The ordered gate stack the PolicyEngine runs; the name of the gate that failed is reported on a denying :class:PolicyVerdict (AC-3-1).

StrictModel

Bases: BaseModel

Base model with strict validation.

This model: - Allows mutation (frozen=False) - Validates assignments - Forbids extra fields - Strips whitespace from strings

Use for internal mutable state.

FrozenModel

Bases: BaseModel

Immutable model (like a frozen dataclass).

This model: - Prevents mutation (frozen=True) - Validates assignments - Forbids extra fields

Use for result objects and immutable data transfer.

GeneratedFile

Bases: FrozenModel

Record of a file generated during a run.

Bundle

Bases: FrozenModel

A narrative bundle about a specific area of the domain.

PhaseTiming

Bases: FrozenModel

Timing data for a single run phase.

TeamConfidence

Bases: FrozenModel

Confidence metadata attached to any team judgment.

Renamed from AnalysisConfidence to keep the framework layer domain-agnostic.

CalibratedJudgment

Bases: FrozenModel

A judgment with attached confidence and auditable reasoning.

AuthorityGrant

Bases: FrozenModel

What a dispatched team is permitted to do (ASTR §4.7).

A capability grant attached to a team's task contract. Every capability defaults to False (deny by default): a caller must explicitly widen a team's authority. The framework enforces may_create_blocking_finding in dispatch_team code — a team whose grant forbids blocking findings cannot persist one, regardless of what its agents' prompts say.

Attributes:

Name Type Description
may_create_blocking_finding bool

Whether this team may persist a Finding with blocking=True. Enforced structurally by dispatch_team (a forbidden blocking finding is stripped from the persisted typed-findings artifact and the dispatch returns an error).

may_write bool

Whether a later-phase specialist coding runtime granted to this team may modify files. A Codex amazon-bedrock runtime gates file writes on this flag (research-coding-runtimes.yaml codex_gpt56_bedrock; consumed in Phase 3 / spec 004-coding-runtime).

may_approve bool

Whether this team may approve/accept work (e.g. mark a finding remediated or sign off an artifact revision).

Budget

Bases: FrozenModel

How much a dispatched team may spend (ASTR §6.2).

The resource envelope attached to a team's task contract. All three limits are non-negative; a negative value is rejected at construction (FrozenModel validates assignments). A value of 0 means "no explicit limit supplied" — dispatch_team only overrides the run-default swarm execution_timeout when max_wall_clock_minutes is strictly positive.

Attributes:

Name Type Description
max_wall_clock_minutes float

Wall-clock ceiling for the team, in minutes. dispatch_team uses max_wall_clock_minutes * 60 as the swarm's execution_timeout when positive.

max_cost_usd float

Dollar ceiling for the team. This is the framework-contract value that a Bedrock ClaudeAgentRuntime maps to the Claude Agent SDK's max_budget_usd option (note the field-name difference: max_cost_usd here vs. max_budget_usd in the SDK — research-coding-runtimes.yaml claude_agent_sdk_bedrock; consumed in Phase 3 / spec 004-coding-runtime).

max_retries int

Retry ceiling. Bounds a Codex harness's retry count when a specialist coding runtime is granted (research-coding-runtimes.yaml codex_gpt56_bedrock; consumed in Phase 3 / spec 004-coding-runtime).

Evidence

Bases: FrozenModel

A single evidence reference backing a finding.

Attributes:

Name Type Description
type EvidenceType

The kind of evidence (file_line, test, command, artifact, url, log).

uri str

A locator for the evidence — e.g. src/auth.py:42 for a file_line ref, a test node id, a command string, an artifact revision id, or a URL. Required and non-empty.

detail str | None

Optional human-readable note about what this evidence shows.

Finding

Bases: FrozenModel

A typed, evidence-bearing finding produced by a team (ASTR §6.6, §8.3).

Immutable and extra="forbid" (inherited from :class:FrozenModel), so a finding is safe to pass by reference through invocation_state and any on-disk findings.json that carries unknown keys is rejected at the parse boundary rather than silently accepted.

Attributes:

Name Type Description
statement str

The finding itself, stated in one or two sentences (non-empty).

severity Severity

One of info/low/medium/high/critical.

blocking bool

Whether this finding must be resolved before the work is accepted. A blocking finding is subject to the evidence gate (must carry ≥1 evidence ref).

disposition Disposition

Lifecycle state (defaults to open).

affected_artifacts list[str]

Paths/ids of artifacts this finding is about.

evidence list[Evidence]

Evidence refs backing the finding (file:line / test / command exit / artifact / url / log).

confidence float

Calibrated confidence in [0.0, 1.0].

created_by str

Identifier of the agent/team that authored the finding.

created_at datetime

UTC creation timestamp.

updated_at datetime

UTC timestamp of the last lifecycle update.

ArtifactRevision

Bases: FrozenModel

An immutable, content-addressed revision of one logical artifact (ASTR §4.8, §6.4).

A lightweight metadata record — not the artifact blob itself, which lives at the layout-resolved path. Immutable and extra="forbid" (inherited from :class:FrozenModel): any attempt to assign a field after construction raises a pydantic.ValidationError (AC-1-1). Build one with :meth:from_content so content_hash and per-facet hashes are computed deterministically from the content (AC-1-2); constructing directly is supported for round-tripping a persisted revision.

Attributes:

Name Type Description
artifact_id str

Stable identity of the logical artifact across all its revisions (defaults to logical_name in :meth:from_content).

revision_id str

Identity of this specific revision. Content-addressed: defaults to content_hash in :meth:from_content. The revision store is keyed by this (AC-1-4).

content_hash str

Deterministic hash of the revision's content alone (AC-1-2).

artifact_type str

The kind of artifact (e.g. "bundle", "finding_set", "patch") — opaque to the framework.

logical_name str

The name the alias pointer maps to a current revision.

produced_by str

Identifier of the team/agent/runtime that produced it.

parents list[str]

revision_ids this revision was derived from (inputs).

supersedes list[str]

revision_ids this revision replaces (lineage; a promotion records the prior current revision here — AC-2-2).

classification str

Handling classification (e.g. "internal").

facets dict[str, str]

Per-facet content hashes ({facet_name: sha256}) enabling the facet-level :func:~strands_teams.revisions.semantic_diff (AC-3-2).

created_at datetime

UTC creation timestamp (not part of content_hash).

from_content classmethod

from_content(
    *,
    logical_name,
    facets=None,
    body=None,
    artifact_type="artifact",
    produced_by="",
    artifact_id=None,
    revision_id=None,
    parents=None,
    supersedes=None,
    classification="internal",
    created_at=None,
)

Build a revision from content, computing content_hash deterministically (AC-1-2).

Supply the content either as facets (a {facet_name: content} mapping, enabling facet-level semantic_diff) or as a single body string (wrapped as one "body" facet). content_hash and revision_id (which defaults to content_hash — content addressing) are derived from the content alone; artifact_id defaults to logical_name.

Parameters:

Name Type Description Default
logical_name str

The logical artifact's name.

required
facets Mapping[str, str] | None

Facet-name to facet-content mapping. Mutually exclusive with body; when both are omitted the revision has empty content.

None
body str | None

A single-blob content string (wrapped as facet "body").

None
artifact_type str

Kind of artifact.

'artifact'
produced_by str

Producer identifier.

''
artifact_id str | None

Stable logical identity; defaults to logical_name.

None
revision_id str | None

Explicit revision id; defaults to content_hash.

None
parents list[str] | None

Input revision ids.

None
supersedes list[str] | None

Superseded revision ids.

None
classification str

Handling classification.

'internal'
created_at datetime | None

Explicit timestamp; defaults to now.

None

Returns:

Type Description
ArtifactRevision

A frozen :class:ArtifactRevision.

Source code in packages/strands-teams/src/strands_teams/models.py
@classmethod
def from_content(
    cls,
    *,
    logical_name: str,
    facets: Mapping[str, str] | None = None,
    body: str | None = None,
    artifact_type: str = "artifact",
    produced_by: str = "",
    artifact_id: str | None = None,
    revision_id: str | None = None,
    parents: list[str] | None = None,
    supersedes: list[str] | None = None,
    classification: str = "internal",
    created_at: datetime | None = None,
) -> ArtifactRevision:
    """Build a revision from content, computing ``content_hash`` deterministically (AC-1-2).

    Supply the content either as ``facets`` (a ``{facet_name: content}``
    mapping, enabling facet-level ``semantic_diff``) or as a single ``body``
    string (wrapped as one ``"body"`` facet). ``content_hash`` and
    ``revision_id`` (which defaults to ``content_hash`` — content addressing)
    are derived from the content alone; ``artifact_id`` defaults to
    ``logical_name``.

    Args:
        logical_name: The logical artifact's name.
        facets: Facet-name to facet-content mapping. Mutually exclusive with
            ``body``; when both are omitted the revision has empty content.
        body: A single-blob content string (wrapped as facet ``"body"``).
        artifact_type: Kind of artifact.
        produced_by: Producer identifier.
        artifact_id: Stable logical identity; defaults to ``logical_name``.
        revision_id: Explicit revision id; defaults to ``content_hash``.
        parents: Input revision ids.
        supersedes: Superseded revision ids.
        classification: Handling classification.
        created_at: Explicit timestamp; defaults to now.

    Returns:
        A frozen :class:`ArtifactRevision`.
    """
    resolved_facets: dict[str, str] = dict(facets) if facets is not None else {}
    if body is not None:
        resolved_facets.setdefault("body", body)
    content_hash = compute_content_hash(resolved_facets)
    facet_hashes = _facet_hashes(resolved_facets)
    return cls(
        artifact_id=artifact_id or logical_name,
        revision_id=revision_id or content_hash,
        content_hash=content_hash,
        artifact_type=artifact_type,
        logical_name=logical_name,
        produced_by=produced_by,
        parents=list(parents or []),
        supersedes=list(supersedes or []),
        classification=classification,
        facets=facet_hashes,
        created_at=created_at or _utcnow(),
    )

StaleArtifactError

StaleArtifactError(logical_name, expected, actual)

Bases: Exception

Raised by promote_artifact when the optimistic compare-and-set fails (AC-2-3).

Signals that expected_current_revision did not match the alias's stored current revision for logical_name — another promotion won the race, or the caller's view of "current" is stale. The alias pointer is left unchanged.

Attributes:

Name Type Description
logical_name

The artifact whose promotion was rejected.

expected

The revision the caller believed was current.

actual

The revision actually current in the alias store.

Record the artifact, the expected revision, and the actually-current one.

Source code in packages/strands-teams/src/strands_teams/models.py
def __init__(self, logical_name: str, expected: str, actual: str) -> None:
    """Record the artifact, the expected revision, and the actually-current one."""
    self.logical_name = logical_name
    self.expected = expected
    self.actual = actual
    super().__init__(
        f"stale promotion of {logical_name!r}: expected current revision {expected!r} but alias holds {actual!r}",
    )

RevisionConflictError

RevisionConflictError(
    revision_id, stored_hash, incoming_hash
)

Bases: Exception

Raised when a revision_id is re-persisted with content differing from the stored one (AC-1-5).

The revision store is content-addressed and append-only: a given revision_id binds to exactly one content_hash. Re-persisting the same revision_id with identical content is idempotent; re-persisting it with differing content is a conflict that leaves the stored revision unchanged.

Attributes:

Name Type Description
revision_id

The revision id that was re-persisted with differing content.

stored_hash

The content hash already on disk for that revision id.

incoming_hash

The content hash of the rejected write.

Record the conflicting revision id and the stored vs. incoming content hashes.

Source code in packages/strands-teams/src/strands_teams/models.py
def __init__(self, revision_id: str, stored_hash: str, incoming_hash: str) -> None:
    """Record the conflicting revision id and the stored vs. incoming content hashes."""
    self.revision_id = revision_id
    self.stored_hash = stored_hash
    self.incoming_hash = incoming_hash
    super().__init__(
        f"revision {revision_id!r} already persisted with content_hash {stored_hash!r}; "
        f"refusing to overwrite with differing content {incoming_hash!r}",
    )

PromotionResult

Bases: FrozenModel

The structured outcome of a successful promote_artifact compare-and-set (AC-2-6).

Returned (as model_dump_json()) by the promote_artifact tool so the calling agent parses JSON per the strands tool norm (research-coding-runtimes line 26).

Attributes:

Name Type Description
logical_name str

The promoted artifact's logical name.

current_revision str

The new current revision_id after promotion.

superseded_revision str

The prior current revision_id (the :data:NO_CURRENT_REVISION sentinel when this was the first revision).

TeamEvent

Bases: FrozenModel

A typed, causal, replayable event on the team event plane (ASTR §6.5, §7).

Immutable and extra="forbid" (inherited from :class:FrozenModel): an unexpected field — notably a raw-transcript or chain-of-thought field — is rejected at construction rather than silently accepted (AC-1-4). Serializing one event to a single JSON line and re-parsing it yields an equal object, so the event log is a lossless round-trip substrate for replay (AC-1-5).

event_id, created_at, and correlation_id are auto-assigned when omitted: a fresh UUID event id, a UTC creation timestamp, and a correlation id defaulting to goal_id when present and otherwise to the assigned event_id (AC-1-3).

Attributes:

Name Type Description
event_id str

Unique identity of this event (auto-assigned UUID hex if omitted).

event_type str

The kind of transition/claim (e.g. "team.dispatched", "critical_finding.created", "goal.decompose") — opaque to the framework except for the should_replan trigger vocabulary.

actor str

Identifier of the agent/team/runtime that produced the event.

recipients list[str]

Identifiers this event is addressed to (empty = broadcast).

thread_id str

The conversation/thread this event belongs to.

goal_id str

The goal (durable lead loop) this event belongs to.

task_id str

The task within the goal this event belongs to.

artifact_bindings list[str]

revision_id/artifact ids this event is about.

causal_parents list[str]

event_ids this event causally follows (replay applies an event only after all its causal parents — AC-3-3).

correlation_id str

Groups related events; defaults to goal_id or event_id (AC-1-3).

epistemic_status EpistemicStatus

The claim's standing (AC-1-2).

payload dict[str, Any]

The structured outcome of the transition — NO transcript/CoT.

evidence list[Evidence]

Evidence refs backing the event (reuses the P1 type).

created_at datetime

UTC creation timestamp (auto-assigned if omitted).

ApprovalToken

Bases: FrozenModel

A revision-bound, signed, expiring approval of a consequential transition (ASTR §14.7).

An approval names the exact :class:ArtifactRevision it approves — by both artifact_revision_id and the revision's immutable revision_content_hash captured at grant time (AC-1-2) — so it goes stale the moment the artifact changes (AC-5-1). Its signature is an HMAC over the canonical serialization of its identity fields (:func:approval_signing_payload), so a forged or tampered token fails :meth:signature_valid and the PolicyEngine treats it as absent (AC-1-4). Past valid_until the engine rejects it as expired (AC-1-5). invalidation_rules names the artifact facets whose change sunsets the approval (AC-5-2, AC-5-3).

Build one with :meth:mint so the signature is computed with the configured signing key; constructing directly is supported for round-tripping a persisted token.

Attributes:

Name Type Description
token_id str

Unique identity of this approval (auto UUID hex when minted).

approved_transition_kind str

The transition kind this token approves (e.g. "release"). An approval for one kind never authorizes another.

artifact_revision_id str

The revision_id of the exact revision approved.

revision_content_hash str

The approved revision's content_hash at grant time — the staleness anchor (AC-5-1).

approver str

Principal who granted the approval (the separation-of-duties and required-approvals gates read this — AC-3-3, AC-3-4).

granted_at datetime

UTC timestamp the approval was minted.

valid_until datetime

UTC expiry; at or after it the token is expired (AC-1-5).

signature str

HMAC-SHA256 hex over :func:approval_signing_payload (AC-1-3).

invalidation_rules list[str]

Facet names whose change sunsets the approval (AC-5-2); empty means any change to the bound artifact invalidates it.

mint classmethod

mint(
    *,
    approved_transition_kind,
    artifact_revision_id,
    revision_content_hash,
    approver,
    signing_key,
    valid_until=None,
    valid_for=None,
    invalidation_rules=None,
    token_id=None,
    granted_at=None,
)

Mint a signed :class:ApprovalToken over its canonical payload (AC-1-3).

Computes the HMAC signature with signing_key so the token verifies against the same key later (AC-1-4). Supply the expiry either as an absolute valid_until or a relative valid_for (added to granted_at); when neither is given the token is valid for 24 hours (AC-1-5).

Parameters:

Name Type Description Default
approved_transition_kind str

The transition kind approved.

required
artifact_revision_id str

The approved revision's id.

required
revision_content_hash str

The approved revision's content hash (AC-1-2).

required
approver str

The approving principal.

required
signing_key str

The configured signing key (from TeamsSettings).

required
valid_until datetime | None

Absolute UTC expiry (wins over valid_for).

None
valid_for timedelta | None

Relative validity window from granted_at.

None
invalidation_rules list[str] | None

Facet names whose change sunsets the approval.

None
token_id str | None

Explicit id; defaults to a fresh UUID hex.

None
granted_at datetime | None

Explicit grant time; defaults to now.

None

Returns:

Type Description
ApprovalToken

A signed, frozen :class:ApprovalToken.

Source code in packages/strands-teams/src/strands_teams/models.py
@classmethod
def mint(
    cls,
    *,
    approved_transition_kind: str,
    artifact_revision_id: str,
    revision_content_hash: str,
    approver: str,
    signing_key: str,
    valid_until: datetime | None = None,
    valid_for: timedelta | None = None,
    invalidation_rules: list[str] | None = None,
    token_id: str | None = None,
    granted_at: datetime | None = None,
) -> ApprovalToken:
    """Mint a signed :class:`ApprovalToken` over its canonical payload (AC-1-3).

    Computes the HMAC ``signature`` with ``signing_key`` so the token verifies
    against the same key later (AC-1-4). Supply the expiry either as an absolute
    ``valid_until`` or a relative ``valid_for`` (added to ``granted_at``); when
    neither is given the token is valid for 24 hours (AC-1-5).

    Args:
        approved_transition_kind: The transition kind approved.
        artifact_revision_id: The approved revision's id.
        revision_content_hash: The approved revision's content hash (AC-1-2).
        approver: The approving principal.
        signing_key: The configured signing key (from ``TeamsSettings``).
        valid_until: Absolute UTC expiry (wins over ``valid_for``).
        valid_for: Relative validity window from ``granted_at``.
        invalidation_rules: Facet names whose change sunsets the approval.
        token_id: Explicit id; defaults to a fresh UUID hex.
        granted_at: Explicit grant time; defaults to now.

    Returns:
        A signed, frozen :class:`ApprovalToken`.
    """
    resolved_id = token_id or uuid4().hex
    resolved_granted = granted_at or _utcnow()
    if valid_until is None:
        valid_until = resolved_granted + (valid_for if valid_for is not None else timedelta(hours=24))
    payload = approval_signing_payload(
        token_id=resolved_id,
        approved_transition_kind=approved_transition_kind,
        artifact_revision_id=artifact_revision_id,
        revision_content_hash=revision_content_hash,
        approver=approver,
        valid_until=valid_until,
    )
    return cls(
        token_id=resolved_id,
        approved_transition_kind=approved_transition_kind,
        artifact_revision_id=artifact_revision_id,
        revision_content_hash=revision_content_hash,
        approver=approver,
        granted_at=resolved_granted,
        valid_until=valid_until,
        signature=_sign(payload, signing_key),
        invalidation_rules=list(invalidation_rules or []),
    )

signing_payload

signing_payload()

Return this token's canonical signing payload (AC-1-3).

Source code in packages/strands-teams/src/strands_teams/models.py
def signing_payload(self) -> str:
    """Return this token's canonical signing payload (AC-1-3)."""
    return approval_signing_payload(
        token_id=self.token_id,
        approved_transition_kind=self.approved_transition_kind,
        artifact_revision_id=self.artifact_revision_id,
        revision_content_hash=self.revision_content_hash,
        approver=self.approver,
        valid_until=self.valid_until,
    )

signature_valid

signature_valid(signing_key)

Return whether signature verifies against signing_key (AC-1-4).

Uses :func:hmac.compare_digest for a constant-time comparison so a signature check cannot be timing-attacked. A token whose signature does not verify is treated as absent by the PolicyEngine.

Source code in packages/strands-teams/src/strands_teams/models.py
def signature_valid(self, signing_key: str) -> bool:
    """Return whether ``signature`` verifies against ``signing_key`` (AC-1-4).

    Uses :func:`hmac.compare_digest` for a constant-time comparison so a
    signature check cannot be timing-attacked. A token whose signature does not
    verify is treated as absent by the ``PolicyEngine``.
    """
    expected = _sign(self.signing_payload(), signing_key)
    return hmac.compare_digest(expected, self.signature)

is_expired

is_expired(now=None)

Return whether the token is at or past its valid_until (AC-1-5).

Source code in packages/strands-teams/src/strands_teams/models.py
def is_expired(self, now: datetime | None = None) -> bool:
    """Return whether the token is at or past its ``valid_until`` (AC-1-5)."""
    return (now or _utcnow()) >= self.valid_until

Decision

Bases: FrozenModel

An auditable record of a consequential choice (ASTR §11).

Captures the full deliberation: the question, the options considered, what was selected, the rationale, the evidence (reusing the P1 :class:Evidence vocabulary — file:line / test / command exit / artifact / url / log, AC-2-2), any dissent, and the prior decision_id this record supersedes when it reverses or replaces an earlier decision (AC-2-3). The PolicyEngine appends one on every allow/deny verdict (AC-2-4).

Attributes:

Name Type Description
decision_id str

Unique identity of this decision (auto UUID hex if omitted).

question str

The question the decision answers.

options list[str]

The options that were considered.

selected str

The option that was chosen.

rationale str

Why the selection was made.

evidence list[Evidence]

Evidence refs backing the decision (reuses the P1 type — AC-2-2).

dissent list[str]

Recorded dissenting views (empty when unanimous).

supersedes list[str]

Prior decision_ids this decision reverses/replaces (AC-2-3).

decided_at datetime

UTC timestamp the decision was recorded.

TransitionRequest

Bases: FrozenModel

A proposed consequential state transition the PolicyEngine judges (ASTR §14.7).

The model constructs one of these and hands it to PolicyEngine.authorize_transition; the engine — never the model — issues the final verdict (AC-6-1). A consequential transition (e.g. a release or a merge) is refused unless a valid revision-bound approval is present and no open blocking finding remains (AC-6-2, AC-6-3).

Attributes:

Name Type Description
actor str

The principal requesting the transition (the capability and separation-of-duties gates read this — AC-3-2, AC-3-3).

kind str

The transition kind (e.g. "release", "merge") — matched against approvals' approved_transition_kind and the human-gated set.

target_revision_id str

The revision_id the transition targets — the revision approvals must bind (AC-3-4) and findings are matched against.

consequential bool

Whether this transition is consequential (release path) and thus subject to the structural approval + blocking-finding refusal (AC-6-2, AC-6-3).

presented_tokens list[str]

The token_ids of the approvals presented for this transition (the engine resolves them against the approval store).

required_capability Literal['may_write', 'may_approve', 'may_create_blocking_finding']

The :class:AuthorityGrant capability this transition requires (defaults to may_approve); the capability gate denies when the actor's grant lacks it (AC-3-2).

target_artifact str

Optional logical artifact name the transition targets, used to match findings that reference the artifact by name as well as by revision id (AC-3-5).

HumanDecision

Bases: FrozenModel

A first-class human-gate protocol state (ASTR §14, §5.2).

Where a transition kind is human-gated, the PolicyEngine emits one of these as pending rather than letting the model self-approve (AC-4-1). The runner persists it to disk and suspends the outer loop for that transition until a human resolves it (AC-4-3, AC-4-5); an approving resolution resumes and re-authorizes the transition (AC-4-4), a rejecting one abandons it (AC-4-6).

Attributes:

Name Type Description
decision_id str

Unique identity of this gate (auto UUID hex if omitted).

transition TransitionRequest

The :class:TransitionRequest this gate governs.

prompt str

The human-readable question posed to the human.

options list[str]

The choices offered to the human.

status HumanDecisionStatus

pending | resolved | rejected (AC-4-2).

resolved_by str

Principal who resolved the gate (empty while pending).

resolved_at datetime | None

UTC timestamp of resolution (None while pending).

created_at datetime

UTC timestamp the gate was emitted.

resolve

resolve(*, approved, resolved_by, resolved_at=None)

Return a resolved copy of this gate (frozen — never mutates in place).

Parameters:

Name Type Description Default
approved bool

True -> resolved (approve); False -> rejected.

required
resolved_by str

The principal resolving the gate.

required
resolved_at datetime | None

Resolution timestamp; defaults to now.

None

Returns:

Type Description
HumanDecision

A new :class:HumanDecision with the updated status and resolver.

Source code in packages/strands-teams/src/strands_teams/models.py
def resolve(self, *, approved: bool, resolved_by: str, resolved_at: datetime | None = None) -> HumanDecision:
    """Return a resolved copy of this gate (frozen — never mutates in place).

    Args:
        approved: ``True`` -> ``resolved`` (approve); ``False`` -> ``rejected``.
        resolved_by: The principal resolving the gate.
        resolved_at: Resolution timestamp; defaults to now.

    Returns:
        A new :class:`HumanDecision` with the updated status and resolver.
    """
    return self.model_copy(
        update={
            "status": "resolved" if approved else "rejected",
            "resolved_by": resolved_by,
            "resolved_at": resolved_at or _utcnow(),
        },
    )

PolicyVerdict

Bases: FrozenModel

The PolicyEngine's final allow/deny on a transition (ASTR §14.7, AC-3-1).

The model never decides — this verdict, produced by PolicyEngine.authorize_transition, is the authority (AC-3-1). On a deny, failed_gate names which gate in the ordered stack refused and reason is human-readable. On an allow, failed_gate is None (AC-3-6). When the denying gate is human_required, pending_human_decision carries the gate the runner must suspend on (AC-4-1).

Attributes:

Name Type Description
allowed bool

Whether the transition is authorized.

failed_gate GateName | None

The gate that refused (None when allowed).

reason str

Human-readable explanation of the verdict.

pending_human_decision HumanDecision | None

The pending gate to suspend on, set only when failed_gate == "human_required" (AC-4-1).

compute_content_hash_from_facet_hashes

compute_content_hash_from_facet_hashes(facet_hashes)

Fold a revision's per-facet hashes into one whole-artifact content_hash.

A stable SHA-256 over the sorted {facet_name: facet_hash} mapping. Because it consumes only the persisted per-facet hashes, the whole-artifact hash is reproducible from a round-tripped record (the model validator uses exactly this), while remaining a deterministic function of content — identical content yields identical facet hashes and thus an identical fold; any differing byte changes at least one facet hash and thus the fold (AC-1-2).

Source code in packages/strands-teams/src/strands_teams/models.py
def compute_content_hash_from_facet_hashes(facet_hashes: Mapping[str, str]) -> str:
    """Fold a revision's per-facet hashes into one whole-artifact ``content_hash``.

    A stable SHA-256 over the sorted ``{facet_name: facet_hash}`` mapping. Because
    it consumes only the persisted per-facet hashes, the whole-artifact hash is
    reproducible from a round-tripped record (the model validator uses exactly
    this), while remaining a deterministic function of content — identical content
    yields identical facet hashes and thus an identical fold; any differing byte
    changes at least one facet hash and thus the fold (AC-1-2).
    """
    canonical = json.dumps(dict(facet_hashes), sort_keys=True, ensure_ascii=False, separators=(",", ":"))
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()

compute_content_hash

compute_content_hash(facets)

Return a deterministic content hash over a revision's facet contents (AC-1-2).

The hash is a function of the facet content alone — not of any metadata (logical_name, produced_by, created_at). Each facet's content is hashed independently, then those per-facet hashes are folded into one digest via :func:compute_content_hash_from_facet_hashes (so the whole-artifact hash and the per-facet hashes on the persisted record always agree). Two revisions built from byte-identical facet content therefore produce equal hashes; any differing byte produces a different hash (AC-1-2).

Parameters:

Name Type Description Default
facets Mapping[str, str]

Mapping of facet name to that facet's content string. An empty mapping hashes to the well-defined fold of {}.

required

Returns:

Type Description
str

A 64-char lowercase hex SHA-256 digest.

Source code in packages/strands-teams/src/strands_teams/models.py
def compute_content_hash(facets: Mapping[str, str]) -> str:
    """Return a deterministic content hash over a revision's facet contents (AC-1-2).

    The hash is a function of the facet *content* alone — not of any metadata
    (``logical_name``, ``produced_by``, ``created_at``). Each facet's content is
    hashed independently, then those per-facet hashes are folded into one digest
    via :func:`compute_content_hash_from_facet_hashes` (so the whole-artifact hash
    and the per-facet hashes on the persisted record always agree). Two revisions
    built from byte-identical facet content therefore produce equal hashes; any
    differing byte produces a different hash (AC-1-2).

    Args:
        facets: Mapping of facet name to that facet's content string. An empty
            mapping hashes to the well-defined fold of ``{}``.

    Returns:
        A 64-char lowercase hex SHA-256 digest.
    """
    return compute_content_hash_from_facet_hashes(_facet_hashes(facets))

approval_signing_payload

approval_signing_payload(
    *,
    token_id,
    approved_transition_kind,
    artifact_revision_id,
    revision_content_hash,
    approver,
    valid_until,
)

Return the canonical serialization an ApprovalToken signs over (AC-1-3).

A stable, sorted JSON object over exactly the six fields the signature binds: token_id, approved_transition_kind, artifact_revision_id, revision_content_hash, approver, and valid_until (serialized as an ISO-8601 string). Deterministic — identical inputs always fold to the same string — so :meth:ApprovalToken.mint and :meth:ApprovalToken.signature_valid sign/verify over byte-identical payloads.

Source code in packages/strands-teams/src/strands_teams/models.py
def approval_signing_payload(
    *,
    token_id: str,
    approved_transition_kind: str,
    artifact_revision_id: str,
    revision_content_hash: str,
    approver: str,
    valid_until: datetime,
) -> str:
    """Return the canonical serialization an ``ApprovalToken`` signs over (AC-1-3).

    A stable, sorted JSON object over exactly the six fields the signature binds:
    ``token_id``, ``approved_transition_kind``, ``artifact_revision_id``,
    ``revision_content_hash``, ``approver``, and ``valid_until`` (serialized as an
    ISO-8601 string). Deterministic — identical inputs always fold to the same
    string — so :meth:`ApprovalToken.mint` and
    :meth:`ApprovalToken.signature_valid` sign/verify over byte-identical payloads.
    """
    canonical = json.dumps(
        {
            "approved_transition_kind": approved_transition_kind,
            "approver": approver,
            "artifact_revision_id": artifact_revision_id,
            "revision_content_hash": revision_content_hash,
            "token_id": token_id,
            "valid_until": valid_until.isoformat(),
        },
        sort_keys=True,
        ensure_ascii=False,
        separators=(",", ":"),
    )
    return canonical