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

@@ -0,0 +1,42 @@
"""AppVersion installer-delivery metadata: sha256 + silentargs.
These two per-version fields feed the endpoint installer push: sha256 pins the
exact bytes the agent fetches, and silentargs carries the version's unattended-
install switches. The API must accept and echo both, and the model must
serialize them.
"""
def _create_app(client, auth_headers, appname='DeliveryApp'):
resp = client.post('/api/applications',
json={'appname': appname, 'isinstallable': True},
headers=auth_headers)
return resp.get_json()['data']['appid']
def test_create_version_accepts_and_echoes_delivery_metadata(client, db, auth_headers):
"""POST versions with sha256 + silentargs; the response carries both back."""
appid = _create_app(client, auth_headers)
digest = 'a' * 64
resp = client.post(f'/api/applications/{appid}/versions',
json={'version': '1.2.3',
'sha256': digest,
'silentargs': '/S /v/qn'},
headers=auth_headers)
assert resp.status_code == 201
data = resp.get_json()['data']
assert data['sha256'] == digest
assert data['silentargs'] == '/S /v/qn'
def test_version_delivery_metadata_defaults_to_null(client, db, auth_headers):
"""A version created without delivery metadata serializes both as null,
so a version that predates the push feature reads cleanly."""
appid = _create_app(client, auth_headers, appname='NoMetaApp')
resp = client.post(f'/api/applications/{appid}/versions',
json={'version': '0.9.0'},
headers=auth_headers)
assert resp.status_code == 201
data = resp.get_json()['data']
assert data['sha256'] is None
assert data['silentargs'] is None

View File

@@ -83,6 +83,15 @@ PUBLIC_ENDPOINTS = {
# requesting user. Same reasoning as endpoints.report_command_result in
# tests/test_core/test_authz.py.
'endpoints.claim_next_command',
# The endpoint agent downloads an installer package for an update_app
# command. Authenticated inside the handler by the endpoints collector API
# key - the same shape as claim_next_command - because a bay has no user
# session. Queueing the update_app command is the actual authority and stays
# gated on endpoints.deploy, audited with the requesting user.
'endpoints.download_app_package',
'endpoints.download_app_package_by_app',
'endpoints.download_script_body',
}
# Reads that need a LOGIN but no named permission, because the handler decides