Files
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

85 lines
2.2 KiB
Python

class ApprovalException(Exception):
"""Base exception for approval-related errors"""
pass
class PreconditionFailed(ApprovalException):
"""Raised when preconditions for applying a change no longer hold"""
pass
class ApplyFailed(ApprovalException):
"""Raised when applying an approved change fails"""
pass
class InvalidIntent(ApprovalException):
"""Raised when intent data is invalid"""
pass
class PolicyEvaluationError(ApprovalException):
"""Raised when policy evaluation fails"""
pass
class ApprovalRequired(ApprovalException):
"""
Raised when an action requires approval.
This exception is raised from serializer methods when approval is required,
allowing the ViewSet to catch it and return the appropriate 409 response.
"""
def __init__(self, change_request, message: str, required_approvers: dict, error_code: str = "approval_required"):
self.change_request = change_request
self.message = message
self.required_approvers = required_approvers
self.error_code = error_code
super().__init__(message)
class PolicyConflict(ApprovalException):
"""
Raised when a single change would require approval under more than one policy.
Raised from serializer methods (mirroring ApprovalRequired) so the ViewSet can catch it and
return a 400 telling the caller to split the change — a single ChangeRequest can only carry
one action's approval, so a change matching several policy-backed actions can't be gated safely.
"""
def __init__(self, conflicting_policies: list, message: str, guidance: str):
self.conflicting_policies = conflicting_policies
self.message = message
self.guidance = guidance
super().__init__(message)
class ChangeRequestError(ApprovalException):
"""Base exception for change request service operations."""
pass
class InvalidStateError(ChangeRequestError):
"""Raised when operation is invalid for the current change request state."""
pass
class AlreadyVotedError(ChangeRequestError):
"""Raised when user has already voted on a change request."""
pass
class ReasonRequiredError(ChangeRequestError):
"""Raised when a reason is required but not provided."""
pass