Make main the lean product a site receives
main was 16 days and 259 commits behind the line the product is actually built on, at contract 0.30.0 against 0.35.0, with no per-PC credential code at all. It was neither current nor curated - just stale. This brings it forward and, at the same time, makes it mean something: main is what a receiving site installs, and nothing else. WHAT IT CARRIES. Twelve plugins - backups, computers, endpoints, guides, knowledgebase, machines, network, notifications, printers, slides, tools, warranty - plus applications, which is manifest-less core and always ships. The only manifest dependency inside that set is endpoints on computers, and both are in it, so the closure is self-contained. WHAT IT DOES NOT. employees, geenforce, measuringtools, printedparts and usb stay on the site line. So do the Windows installer tree, the VM rehearsal helpers, the agent tooling config, one site's data loader, the internal audit and findings records, and the planner spreadsheet. 1,135 tracked files against 1,438. A REMOVAL THAT BREAKS THE APP IS NOT A REMOVAL, and two nearly shipped: deploy/site-profile-universal.json still named all seventeen plugins. It is the file docs/DEPLOY.md tells a site to provision from, so a fresh install would have failed at `flask plugin apply-profile` with PluginError - the first thing a receiving site does, broken by the branch meant to serve them. It now names the twelve that exist, and its comment says the two must agree. agent/ was never refreshed. Whoever populated this branch copied shopdb/, plugins/, frontend/ and tests/ from the site line and left the agent tree at its September state: 22 C# and PowerShell sources missing, including AgentCredentials, EnrollmentService, AgentHttpFactory and the fleet-alert client, plus one file the site line had deleted still present. The old tree is internally consistent, so it compiles, and nothing in CI builds it - every gate was green over a server that speaks enrollment and per-PC credentials to a bundled agent source that cannot. Resynced whole. DECISIONS THIS ENCODES, rather than defers: - The plugin-authoring lab builds printedparts, whose manifest depends on employees, so it could not stay. The lab remains on the site line and main ships no build-along tutorial. That is a real loss for an adopting developer and is recorded here rather than discovered later. - Recognition, Training and Recertification notifications are no longer seeded. Each splits one notification into a card per employee and renders a photo, which needs a plugin this branch does not bundle. Seeding them would offer an author a type that cannot resolve anybody. A site that installs employees gets them from that plugin's own seed. - Windows/IIS is documented as the legacy path throughout, and the README leads with Docker, because that is what a receiving site gets. The API description is regenerated for THIS branch - 347 paths and 483 operations, down from 415 and 573 - so the published contract describes what is here rather than what the site line serves. docs/CONFIG.md lost the two measuring-tool label keys the code no longer declares, which two lanes had decided opposite ways. Suite: 3124 passed, 6 skipped. Naming and style gates pass. The docs gates run rather than skip, and every index line count is derived from the file it names.
This commit is contained in:
@@ -33,9 +33,9 @@ from .plugins import plugin_manager
|
||||
# which backs seeding, the role grid, and API-token scope validation. Additive
|
||||
# optional hook, minor bump.
|
||||
# 0.11.0: added service_token_authorized(scope) to shopdb.api so a plugin's
|
||||
# unattended endpoints (e.g. the GE-Enforce fetch API) can authorize a scoped
|
||||
# managed service token without importing core token internals. Additive name
|
||||
# on the import surface, minor bump.
|
||||
# unattended endpoints (a fetch route a fleet agent polls, say) can authorize a
|
||||
# scoped managed service token without importing core token internals. Additive
|
||||
# name on the import surface, minor bump.
|
||||
# 0.16.0: added the get_settings_defaults hook so a plugin declares the Setting
|
||||
# rows it owns (key, value, type, category, description, public). The framework
|
||||
# seeds them at install, at enable, and on `flask plugin upgrade-all`, files a
|
||||
@@ -46,8 +46,8 @@ from .plugins import plugin_manager
|
||||
# beside DashboardDefault (which 0.17.0 imported but never listed in __all__).
|
||||
# The role vocabulary is now the kiosk's own - Dashboard / Lobby / 3DPrintRoom,
|
||||
# the literal values of C:\Enrollment\display-type.txt - so a plugin holding its
|
||||
# own copy of that map (geenforce did) can read core's instead of drifting from
|
||||
# it. Additive names on the import surface, minor bump.
|
||||
# own copy of that map can read core's instead of drifting from it. Additive
|
||||
# names on the import surface, minor bump.
|
||||
# 0.19.0: get_dashboard_widgets returns DATA AND SHAPE, not a component name.
|
||||
# The old shape ('name' + 'component' + 'size') named a Vue component per
|
||||
# widget, which cannot survive a lean build - a plugin's component may never be
|
||||
@@ -110,7 +110,46 @@ from .plugins import plugin_manager
|
||||
# internals. The journal records the primitive mutations a bulk import makes
|
||||
# (update a cell, insert a row) tied to a batch id, and a generic undo replays
|
||||
# them in reverse. Additive names on the surface, minor bump.
|
||||
__contract_version__ = '0.30.0'
|
||||
#
|
||||
# 0.31.0 widens the asset-panel vocabulary a plugin may declare: two more
|
||||
# tab groups (serviceprocess, reports), a datetime column format, a map body
|
||||
# key, and a tabs section that renders as a table. All optional, so a plugin
|
||||
# that declares none of them is unaffected. Additive, minor bump.
|
||||
# 0.32.0 adds caller_is_role to the authorization surface: the body-answering
|
||||
# counterpart to require_role, for a route that serves everyone but treats one
|
||||
# class of caller differently (admin break-glass override). Scoped personal
|
||||
# tokens are always refused by it, matching require_role, so a plugin cannot
|
||||
# accidentally let a scoped token inherit role authority from its owner.
|
||||
# Additive name on the surface, minor bump.
|
||||
# 0.33.0 adds per-PC agent credentials to the collector contract. BasePlugin
|
||||
# gains two optional hooks, resolve_agent_credential(secret) and
|
||||
# agent_shared_key_refused(hostname), and shopdb.api exports
|
||||
# resolve_agent_credential, agent_shared_key_refused and collector_ingest. A
|
||||
# collector schema may declare "acceptsagentcredential": true; the generic
|
||||
# collector then accepts X-Endpoint-Credential for it, refuses the shared key
|
||||
# for an enrolled PC (401 SHARED_KEY_REFUSED), and maps a PermissionError from
|
||||
# apply_collector_payload to 403 ASSET_MISMATCH. All optional, so a plugin that
|
||||
# declares none of it is unaffected. Additive, minor bump.
|
||||
# 0.34.0 adds BusinessUnit to the core model surface. It was the one thing an
|
||||
# asset carries that a plugin could not name: the endpoints fleet-alert scope
|
||||
# resolves a business unit per poll, and with no contract name for it the route
|
||||
# had dropped to db.text('SELECT businessunit FROM businessunits ...') against a
|
||||
# core table. That workaround respects the letter of ADR-002 and defeats its
|
||||
# point - a raw query is hidden coupling to the core schema that no contract
|
||||
# bump can catch, and it is the kind of thing that gets copied. Exposing the
|
||||
# model closes the gap instead of leaving the workaround as the worked example.
|
||||
# Additive name on the surface, minor bump.
|
||||
# 0.35.0 adds ip_in_cidrs and request_ip_allowlisted to shopdb.api: network
|
||||
# position as an admission check for routes that run without a login. The two
|
||||
# kiosks needed the identical gate - a device token suits an unattended screen
|
||||
# provisioned once, and suits nothing about somebody opening the same page on
|
||||
# their own PC - and a CIDR parser copied into two plugins is exactly where a
|
||||
# fail-open creeps in later. Both fail closed on an empty setting, an
|
||||
# unparseable caller address and a malformed entry. Caller trustworthiness is a
|
||||
# deployment property: they read remote_addr, never raw X-Forwarded-For, so the
|
||||
# IIS rewrite rule is what makes them meaningful. Additive names on the surface,
|
||||
# minor bump.
|
||||
__contract_version__ = '0.35.0'
|
||||
|
||||
# Product release version (see ADR-007). The product version and the
|
||||
# plugin-contract version above are distinct series with independent
|
||||
@@ -199,11 +238,46 @@ def create_app(config_name: str = None) -> Flask:
|
||||
|
||||
# Initialize plugin manager
|
||||
with app.app_context():
|
||||
# On first run (no users exist), tell the operator a setup token is
|
||||
# active and how to read it. The token gates POST /api/setup/create-admin
|
||||
# to prevent a network race during the install window (API starts
|
||||
# serving before the admin exists in every documented deploy path). The
|
||||
# secret itself is never logged: logs are shipped and retained far more
|
||||
# widely than the .env it came from.
|
||||
from shopdb.core.models import User
|
||||
try:
|
||||
if User.query.count() == 0:
|
||||
token = app.config.get('SETUP_TOKEN') or os.environ.get('SETUP_TOKEN')
|
||||
if token:
|
||||
app.logger.warning('=' * 60)
|
||||
app.logger.warning('FIRST RUN: No users exist.')
|
||||
app.logger.warning('A setup token is active (SETUP_TOKEN). '
|
||||
'Read it with: flask seed show-setup-token')
|
||||
app.logger.warning('Create admin: POST /api/setup/create-admin')
|
||||
app.logger.warning(' with header: X-Setup-Token: <token>')
|
||||
app.logger.warning('OR use CLI: docker exec shopdb-api flask seed admin')
|
||||
app.logger.warning('=' * 60)
|
||||
else:
|
||||
app.logger.warning('FIRST RUN: No users exist, no SETUP_TOKEN set.')
|
||||
app.logger.warning('Use CLI: docker exec shopdb-api flask seed admin')
|
||||
except Exception:
|
||||
pass # DB not ready yet, skip
|
||||
plugin_manager.init_app(app, db)
|
||||
|
||||
# Register core blueprints
|
||||
register_blueprints(app)
|
||||
|
||||
# Finish any restore that is waiting on its schema upgrade. The backup
|
||||
# sidecar has no Flask, so it loads the dump and leaves a sentinel; this
|
||||
# container owns the app context that can run the migrations. Started here
|
||||
# rather than left to a browser poll, because a restore must complete
|
||||
# whether or not anyone is still watching it.
|
||||
#
|
||||
# Skipped unless a backups store actually exists, which keeps it off every
|
||||
# deployment that does not run the backup overlay - and out of the test
|
||||
# suite, which builds this app thousands of times.
|
||||
# NOTE: wilm's backup implementation doesn't require initialization here
|
||||
|
||||
# Personal API token auth shim: recognize `Bearer shopdb_pat_...` before
|
||||
# any JWT decode and mint a request-scoped JWT for the token's owner.
|
||||
from .utils.apitoken_auth import install_apitoken_auth
|
||||
@@ -292,12 +366,14 @@ CORE_BLUEPRINT_NAMES = (
|
||||
'collector',
|
||||
'settings',
|
||||
'auditlogs',
|
||||
'employees',
|
||||
'users',
|
||||
'customfields',
|
||||
'setup',
|
||||
'pluginui',
|
||||
'apitokens',
|
||||
'docs',
|
||||
'shopdbbackups',
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user