8fe38dc661
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
- Reopening a test for rework (red or blue) now archives the round that's ending into a new test_round_history table before resetting the live fields — procedure/results/detection data is preserved instead of overwritten, and Phase Timeline renders every past round alongside the current one, so Blue Team keeps visibility into earlier Red attempts. - Each archived round also posts a permanent Jira comment (push_round_archived) since Jira's custom fields only ever show the latest value once the next round resubmits — the comment thread is what preserves full history there. - push_pause_event no longer transitions the Jira issue to "On Hold" — a timer pause is not a real Hold, and flipping Jira status for it was misleading. Only the explicit Hold action (push_hold_event) does that now.
61 lines
2.8 KiB
Python
61 lines
2.8 KiB
Python
"""SQLAlchemy model for archived test rounds.
|
|
|
|
Each row is a snapshot of one team's round of work (procedure/results for
|
|
Red, detection/containment for Blue) taken right before a lead reopens the
|
|
test for rework. Reopening resets the live fields on ``Test`` for a fresh
|
|
attempt, but the prior attempt's data must not be lost — it's archived here
|
|
so the full history stays visible (e.g. to Blue Team, who need to see what
|
|
Red actually did on earlier rounds, not just the latest one).
|
|
"""
|
|
|
|
import uuid
|
|
|
|
from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String, Text, func
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.database import Base
|
|
from app.models.enums import AttackSuccessResult, ContainmentResult, TestResult
|
|
|
|
|
|
class TestRoundHistory(Base):
|
|
"""Archived snapshot of one red/blue round of a test, taken on reopen."""
|
|
|
|
__tablename__ = "test_round_history"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
test_id = Column(UUID(as_uuid=True), ForeignKey("tests.id"), nullable=False)
|
|
team = Column(String(10), nullable=False) # "red" or "blue"
|
|
round_number = Column(Integer, nullable=False)
|
|
|
|
started_at = Column(DateTime, nullable=True)
|
|
ended_at = Column(DateTime, nullable=True)
|
|
paused_seconds = Column(Integer, default=0)
|
|
|
|
# Red-side round output
|
|
procedure_text = Column(Text, nullable=True)
|
|
tool_used = Column(String, nullable=True)
|
|
# native_enum=False — stored as plain VARCHAR, not the shared Postgres
|
|
# enum type used by tests.attack_success. This is an archive table with
|
|
# no DB-level constraint needs, and reusing the native type name here
|
|
# would make Alembic try to (re)create it.
|
|
attack_success = Column(Enum(AttackSuccessResult, name="test_round_history_attack_success", native_enum=False), nullable=True)
|
|
red_summary = Column(Text, nullable=True)
|
|
execution_start_time = Column(DateTime, nullable=True)
|
|
execution_end_time = Column(DateTime, nullable=True)
|
|
|
|
# Blue-side round output
|
|
detection_result = Column(Enum(TestResult, name="test_round_history_detection_result", native_enum=False), nullable=True)
|
|
containment_result = Column(Enum(ContainmentResult, name="test_round_history_containment_result", native_enum=False), nullable=True)
|
|
detection_time = Column(DateTime, nullable=True)
|
|
containment_time = Column(DateTime, nullable=True)
|
|
blue_summary = Column(Text, nullable=True)
|
|
|
|
# Why the round was closed
|
|
review_notes = Column(Text, nullable=True)
|
|
reviewed_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
|
archived_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
test = relationship("Test", back_populates="round_history")
|
|
reviewer = relationship("User", foreign_keys=[reviewed_by])
|