mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-19 23:01:29 +02:00
A target carries a list of host names, addresses and URLs an operator already holds. Every scan of that target starts from them, so a list earned elsewhere no longer has to be re-pasted or re-discovered. Each pasted line is classified by its own shape and scope-checked against the target; a refusal names the line and the reason. Seeds enter a run through the asset_seed stage, which resolves the names it has no answer for and materialises what they resolve to. Stages the engine has switched off still do not run. Seeds no longer force a focused run: scope reads the new seed_only, which only rescan, watch and connector runs set, so a seeded census scan still covers its target. Seed count is a material run facet, so Compare states it before any delta it would otherwise explain. Two stages share the subdomains table for a run: discovery's clear no longer drops rows asset_seed wrote, and the run's seeded hosts join its merge so the imported source survives a name discovery also finds. Closes #1538 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYZfaNjq3Ym8GPvWYuHisS
54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel
|
|
from pydantic import Field as PydanticField
|
|
from sqlmodel import Field, SQLModel, UniqueConstraint
|
|
|
|
from shared.definitions.rescan import MAX_RUN_ASSETS, SeedKind
|
|
from shared.utils.datetime import utc_now
|
|
|
|
MAX_SEED_VALUE_LEN = 2000
|
|
|
|
|
|
class TargetSeed(SQLModel, table=True):
|
|
__tablename__ = "target_seeds"
|
|
__table_args__ = (
|
|
UniqueConstraint("target_id", "value", name="uq_target_seed_value"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
target_id: uuid.UUID = Field(
|
|
foreign_key="targets.id", index=True, ondelete="CASCADE"
|
|
)
|
|
project_id: uuid.UUID = Field(foreign_key="projects.id", index=True)
|
|
kind: str = Field(default=SeedKind.HOST.value, max_length=16)
|
|
value: str = Field(max_length=MAX_SEED_VALUE_LEN)
|
|
created_at: datetime = Field(default_factory=utc_now)
|
|
|
|
|
|
class TargetSeedRead(BaseModel):
|
|
id: uuid.UUID
|
|
kind: str
|
|
value: str
|
|
created_at: datetime
|
|
|
|
|
|
class TargetSeedWrite(BaseModel):
|
|
"""Seed lines as typed or pasted. Each line is classified against the target."""
|
|
|
|
values: list[str] = PydanticField(default_factory=list, max_length=MAX_RUN_ASSETS)
|
|
replace: bool = False
|
|
|
|
|
|
class TargetSeedRejection(BaseModel):
|
|
value: str
|
|
reason: str
|
|
|
|
|
|
class TargetSeedResult(BaseModel):
|
|
total: int
|
|
added: int
|
|
removed: int
|
|
rejected: list[TargetSeedRejection] = PydanticField(default_factory=list)
|