Merge the wilm delivery work into main

Ben Baker's installed-software collection, software delivery (run_script and
update_app), agentless adoption tool and command-queue hardening, with his
authorship preserved.

Reviewed before promotion. Fixed: the endpoints plugin tests were never collected
by pytest, the SBOM snapshot table's key would exhaust on MySQL, ingest did not
truncate two columns strict MySQL would reject, and update_app's version branch
pinned a digest for a file that route never serves. Agent packaging moved off
require_role('admin') onto a new endpoints.agentpackage permission.

Contract 0.27.0. Full suite 2231, frontend 314.
This commit is contained in:
2026-08-30 19:41:15 -04:00
parent 8f4fe74dcb
commit 6680c3c5de
59 changed files with 5557 additions and 68 deletions

View File

@@ -34,6 +34,11 @@ class Application(BaseModel):
comment='Required on all PCs (drives the software-compliance report)'
)
image = db.Column(db.String(255))
# Digest of the app-level uploaded installer (installpath). Lets the
# endpoint update_app push resolve an install source by app alone, with no
# AppVersion row: the agent fetches the pinned bytes and verifies this hash.
# Stamped on package upload; null when installpath is a site share path.
installersha256 = db.Column(db.String(64))
# Relationships
supportteam = db.relationship('SupportTeam', back_populates='applications')
@@ -70,6 +75,12 @@ class AppVersion(db.Model):
notes = db.Column(db.String(255))
dateadded = db.Column(db.DateTime, default=_utcnow)
isactive = db.Column(db.Boolean, default=True)
# Delivery metadata for the endpoint installer push. sha256 pins the exact
# bytes the agent must fetch (warn-not-block for now; hardened later);
# silentargs carries this version's unattended-install switches because
# homegrown EXEs vary by developer (MSI is uniform via msiexec).
sha256 = db.Column(db.String(64))
silentargs = db.Column(db.String(255))
# Relationships
application = db.relationship('Application', back_populates='versions')
@@ -88,7 +99,9 @@ class AppVersion(db.Model):
'releasedate': self.releasedate.isoformat() if self.releasedate else None,
'notes': self.notes,
'dateadded': self.dateadded.isoformat() + 'Z' if self.dateadded else None,
'isactive': self.isactive
'isactive': self.isactive,
'sha256': self.sha256,
'silentargs': self.silentargs
}
def __repr__(self):