Add application-driven policy generation with an acknowledgement gate

Ticking "Required on all PCs" now generates a configuration policy that
installs the application on every non-server endpoint it matches, rather
than only flagging a software-compliance report row.

Because that save is a fleet-wide deployment, it requires explicit
acknowledgement. The gate is enforced in the API, not only in the form:
a confirmation dialog that can be sidestepped by POSTing directly is
decoration, not a safeguard. It fires only on the transition INTO the
required state, since re-saving an already-required application deploys
nothing new.

Generated policies carry a new 'autoapp' source. They stay editable, and
hand-editing one transfers ownership to 'local' so it survives a later
uncheck; 'shipped' remains read-only. Existing required applications are
not migrated retroactively - policies are only created on save.

Also adds the monitorable fields that the shipped-policy breakup will
build on, and repairs a mangled non-ASCII byte in copilot-instructions.md
that was failing the style gate.
This commit is contained in:
benjamin.baker
2026-09-07 13:27:59 -04:00
parent f73df42110
commit aa9dafc5ca
10 changed files with 497 additions and 7 deletions

View File

@@ -0,0 +1,110 @@
"""Tests for the acknowledgement gate on "Required on all PCs".
Ticking that box is not a reporting flag. It generates a configuration policy
that installs the application on every non-server endpoint it matches, so the
save is a fleet-wide deployment. The gate exists so nobody triggers one by
tabbing through a form.
The gate lives in the API rather than only in the form on purpose, and that is
most of what is pinned here: a confirmation dialog that can be sidestepped by
POSTing directly is decoration, not a safeguard. The other half is that it
fires only on the transition INTO the required state, since re-saving an app
that already has its policy deploys nothing new.
"""
from shopdb.core.models import Application
def _app(db, name='Test App', isrequired=False):
app = Application(appname=name, isrequired=isrequired)
db.session.add(app)
db.session.commit()
return app
def test_create_marked_required_is_refused_without_acknowledgement(
client, db, auth_headers):
response = client.post('/api/applications',
json={'appname': 'Fleet App', 'isrequired': True},
headers=auth_headers)
assert response.status_code == 400, response.get_json()
message = response.get_json()['data']['error']['message']
assert 'install' in message.lower()
assert Application.query.filter_by(appname='Fleet App').first() is None
def test_create_marked_required_succeeds_once_acknowledged(
client, db, auth_headers):
response = client.post('/api/applications',
json={'appname': 'Fleet App', 'isrequired': True,
'acknowledgepolicy': True},
headers=auth_headers)
assert response.status_code == 201, response.get_json()
assert Application.query.filter_by(appname='Fleet App').first().isrequired
def test_update_into_required_is_refused_without_acknowledgement(
client, db, auth_headers):
app = _app(db)
response = client.put(f'/api/applications/{app.appid}',
json={'isrequired': True}, headers=auth_headers)
assert response.status_code == 400, response.get_json()
# The refusal must leave the flag alone, not half-apply the change.
assert db.session.get(Application, app.appid).isrequired is False
def test_update_into_required_succeeds_once_acknowledged(
client, db, auth_headers):
app = _app(db)
response = client.put(f'/api/applications/{app.appid}',
json={'isrequired': True, 'acknowledgepolicy': True},
headers=auth_headers)
assert response.status_code == 200, response.get_json()
assert db.session.get(Application, app.appid).isrequired is True
def test_resaving_an_already_required_app_needs_no_acknowledgement(
client, db, auth_headers):
# The policy already exists, so this save deploys nothing new. Prompting
# again would train people to click through the warning that matters.
app = _app(db, isrequired=True)
response = client.put(f'/api/applications/{app.appid}',
json={'isrequired': True,
'appdescription': 'Edited'},
headers=auth_headers)
assert response.status_code == 200, response.get_json()
def test_clearing_required_needs_no_acknowledgement(client, db, auth_headers):
# Unticking withdraws the policy. It never installs anything, so there is
# nothing to warn about.
app = _app(db, isrequired=True)
response = client.put(f'/api/applications/{app.appid}',
json={'isrequired': False}, headers=auth_headers)
assert response.status_code == 200, response.get_json()
assert db.session.get(Application, app.appid).isrequired is False
def test_acknowledgement_flag_is_not_persisted_to_the_application(
client, db, auth_headers):
# It is a control flag for one request, not a column. If it ever reached
# the model it would be a stored "yes" that suppresses future warnings.
response = client.post('/api/applications',
json={'appname': 'Fleet App', 'isrequired': True,
'acknowledgepolicy': True},
headers=auth_headers)
assert response.status_code == 201, response.get_json()
app = Application.query.filter_by(appname='Fleet App').first()
assert not hasattr(app, 'acknowledgepolicy')
assert 'acknowledgepolicy' not in response.get_json()['data']