geenforce: gate publishing on the library version, not on the manifest's own

The publish gate exists because a minor version bump that NARROWS behaviour is
not backward compatible: _CmmVersion arrived in lib 2.6, and an older lib does
not know the field, so every gated entry looks unfiltered and it installs every
PC-DMIS version it cannot detect, on every CMM, in one cycle.

It was comparing the fleet's reported library versions against manifestversion.
That is the manifest's own 'Version' field. For a share-imported manifest the
two numbering schemes happen to coincide; for a scope authored in code they do
not, and seed_display_scope writes '2.0' - which every kiosk exceeds. So the
gate passed on the scope that most needed it.

A scope now declares minlibversion. Unset, the requirement is DERIVED from what
the manifest actually uses, so a scope written before this column existed is
still judged on its contents rather than on a number that says nothing about the
library. Only features that narrow behaviour belong in that table; an addition
an old lib ignores harmlessly needs no floor. manifestversion remains the last
fallback, which preserves what share-imported manifests already relied on.
This commit is contained in:
cproudlock
2026-08-14 13:47:11 -04:00
parent 2df5028883
commit 838932a72d
5 changed files with 148 additions and 5 deletions

View File

@@ -0,0 +1,49 @@
"""A scope declares the client library it needs, separately from its own version.
The publish gate exists to refuse a manifest the fleet's GE-Enforce library
cannot read correctly. It was comparing reported library versions against
`manifestversion` - the manifest's own 'Version' field, which for a share
imported manifest happens to share the library's numbering but for a
code-authored scope is whatever the author chose (seed_display_scope writes
'2.0'). Every kiosk runs something newer than that, so the gate passed on the
one scope most in need of it.
`minlibversion` says the thing the gate actually wants to know. NULL means
derive it from what the manifest uses, so existing scopes need no edit.
Revision ID: geenforce0004minlib
Revises: geenforce0003subtype
"""
from alembic import op
import sqlalchemy as sa
revision = 'geenforce0004minlib'
down_revision = 'geenforce0003subtype'
branch_labels = None
depends_on = None
TABLE = 'manifestscopes'
def _columns(bind):
insp = sa.inspect(bind)
if TABLE not in insp.get_table_names():
return None
return {c['name'] for c in insp.get_columns(TABLE)}
def upgrade():
# Guarded like geenforce0003subtype and network0003prefix: on a fresh
# database the table is built from the models, which already declare this.
columns = _columns(op.get_bind())
if columns is None or 'minlibversion' in columns:
return
op.add_column(TABLE, sa.Column('minlibversion', sa.String(length=16),
nullable=True))
def downgrade():
columns = _columns(op.get_bind())
if columns is None or 'minlibversion' not in columns:
return
op.drop_column(TABLE, 'minlibversion')