"""What a bay actually HAS: one row per printer queue a PC reported. This is the observed half of the printer loop. The assigned half already exists as usesprinter/defaultprinter relationship rows on the machine, and the two are kept apart on purpose: the moment a drifted bay's observed state is allowed to write assignment rows, enforcement stops meaning anything. Nothing in this table is desired state, and no code may promote it to desired state without a person asking for that explicitly. Current state, not history. The latest report for a host REPLACES every row that host had before, so "what does this bay have" is a plain filter and never a question about time. An append-only table would grow with every GE-Enforce cycle and answer that question wrong. The audit log already records each ingest, which is where the history lives. Rows are keyed by hostname as reported, with assetid as a resolved convenience: a bay can report before anyone creates its computer record, and the report must still land. Matching an observed queue back to a ShopDB printer asset happens at READ time (port address first, then queue name) so a printer added tomorrow matches without the bay re-reporting. Replacement is a hard DELETE of the host's rows, not a soft one: the inherited isactive flag is not a soft-delete marker here, because a queue that is gone from the bay is not observed state that has been retired, it is state that was never observed again. """ from shopdb.api import db, BaseModel class PrinterObservedQueue(BaseModel): """One Windows print queue seen on one reporting PC at one point in time.""" __tablename__ = 'printerobservedqueues' printerobservedqueueid = db.Column(db.Integer, primary_key=True) # The reporting PC, resolved at ingest. Nullable because an unenrolled bay # still gets to report, and no backref: core assets must not grow a # dependency on this plugin. assetid = db.Column( db.Integer, db.ForeignKey('assets.assetid', ondelete='CASCADE'), nullable=True, comment='Reporting PC asset, resolved from hostname at ingest', ) asset = db.relationship('Asset', lazy='select', viewonly=True) # Authoritative identity of the report, stored as sent. assetid can be null # or can go stale after a rename; hostname is what the replace keys on. hostname = db.Column( db.String(255), nullable=False, comment='Reporting PC hostname, as sent by the collector', ) queuename = db.Column( db.String(255), nullable=False, comment='Windows printer (queue) name', ) drivername = db.Column( db.String(255), comment='Windows driver name, verbatim; comparable to printerdrivers.drivername', ) portname = db.Column( db.String(255), comment='Windows port name, freeform', ) # Primary match key: an IP or FQDN identifies a device unambiguously, where # a queue name is only ever a convention. Null for non-TCP/IP ports. portaddress = db.Column( db.String(255), comment='Host address the port points at (IP or FQDN)', ) isdefault = db.Column( db.Boolean, nullable=False, default=False, comment='Was the default queue for the reporting context', ) isshared = db.Column( db.Boolean, nullable=False, default=False, comment='Queue is shared off this PC', ) # Server-stamped once per report, so every row of one report carries the # same value and "when did this bay last report" needs no aggregate. observedat = db.Column( db.DateTime, nullable=False, comment='When the report that produced this row was ingested', ) __table_args__ = ( # Windows queue names are unique per host, so this doubles as the read # index for the hostname filter and turns a botched partial replace into # an IntegrityError instead of silent duplicate queues. db.UniqueConstraint('hostname', 'queuename', name='uq_printerobservedqueue_host_queue'), db.Index('idx_printerobservedqueues_portaddress', 'portaddress'), db.Index('idx_printerobservedqueues_assetid', 'assetid'), ) def __repr__(self): return f""