Commit Graph

4 Commits

Author SHA1 Message Date
cproudlock
d20682fd06 Extract employee directory into a plugin
Third core feature pulled into a plugin (blueprint-only, like slides). The
employee directory is a read-only lookup over a separate HR database.

- plugins/employees/: manifest (api_prefix /api/employees, no deps), api/ (moved
  blueprint, contract-pure: success/error/ErrorCodes + employee_connection all
  from shopdb.api), plugin.py (get_blueprint, get_models -> []).
- employee_connection STAYS core infrastructure in shopdb.api (config-driven
  external DB connector, shared by search + the notifications shopfloor feed). So
  no get_services needed and no contract change - the plugin owns the directory
  FEATURE, core owns the shared connector.
- Fixed a latent bug in the move: error paths used ErrorCodes.DATABASE_ERROR
  which does not exist -> ErrorCodes.INTERNAL_ERROR (so a directory outage now
  returns a clean 500 envelope instead of an AttributeError crash).
- De-cored: deleted shopdb/core/api/employees.py, removed from
  CORE_BLUEPRINT_NAMES + core/api/__init__ import/__all__. Registered in
  instance/plugins.json.

Pinned first: validation (400) + graceful-degrade (500) characterization tests;
the degrade test caught the DATABASE_ERROR bug and goes green with the fix.
184 tests pass, naming green, app boots 9 bundled plugins, endpoint verified live.

Plugin extractions complete: knowledgebase, slides, employees.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:13:15 -04:00
cproudlock
b6ec4cb577 Extract slideshow into a plugin (blueprint-only display plugin)
Second core feature pulled into a plugin. Slides is the minimal plugin shape:
a blueprint only - no model, no AssetType, no nav (it is purely an API surface
consumed by the TV dashboard).

- plugins/slides/: manifest (api_prefix /api/slides, no deps), api/ (moved
  blueprint, contract-pure imports via shopdb.api, same /api/slides route so the
  TV dashboard is unchanged), plugin.py (get_blueprint, get_models -> []).
- Fixed a latent bug in the move: error path used ErrorCodes.SERVER_ERROR which
  does not exist -> ErrorCodes.INTERNAL_ERROR.
- De-cored: deleted shopdb/core/api/slides.py, removed from CORE_BLUEPRINT_NAMES
  + core/api/__init__ import/__all__. Registered in instance/plugins.json.

Pinned with a characterization test first; passes unchanged against the plugin
blueprint. 174 tests pass, naming green, app boots 8 bundled plugins, /api/slides
verified live.

Note: the shopfloor dashboard is NOT extractable - it is a frontend-only Vue view
aggregating the businessunits + notifications APIs, with no backend to move and no
frontend plugin system.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:50:32 -04:00
cproudlock
a6f6c3f51f Extract Knowledge Base into a plugin (reference non-asset plugin)
First feature extracted from core into a plugin per "plugin is the product",
mirroring the notifications plugin. KB is a NON-asset plugin: it contributes a
model + blueprint + nav item but registers no AssetType.

- plugins/knowledgebase/: manifest.json (api_prefix /api/knowledgebase, no deps),
  models/ (KnowledgeBase, contract-pure imports via shopdb.api), api/ (the
  blueprint, same routes/prefix so the frontend is unchanged), plugin.py
  (get_blueprint + get_models + get_navigation_items).
- De-cored: removed shopdb/core/models/knowledgebase.py + api/knowledgebase.py,
  their __init__ exports, and 'knowledgebase' from CORE_BLUEPRINT_NAMES; dropped
  the hardcoded KB nav item from dashboard.py (now via the plugin nav hook).
- search.py and reports.py lazy-import KnowledgeBase from the plugin and degrade
  gracefully (search skips via _require_enabled when disabled; kb-popularity
  report returns 503 if the plugin is absent).
- Registered in instance/plugins.json (enabled).

The knowledgebase table stays in the core Alembic chain (bundled-plugin schema
folded into core, ADR-004); the model just maps it. KB was never in the
shopdb.api contract surface, so no __contract_version__ bump.

Pinned with characterization tests first (test_knowledgebase.py); they pass
unchanged against the plugin blueprint. 163 tests pass, naming green, app boots
7 bundled plugins, KB endpoint/nav/search verified live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:32:37 -04:00
cproudlock
c70bc37642 Phase 1: pytest baseline, production hardening, pinned requirements
Establishes the safety net required before any structural refactor.

Tests (tests/):
- conftest.py rewritten for Flask-SQLAlchemy 3.x (drop-recreate per
  test, StaticPool-shared in-memory SQLite, admin_user + auth_headers
  fixtures). Removes deprecated db.create_scoped_session pattern.
- test_smoke.py: 8 baseline tests (app boot, JWT login valid+invalid,
  protected routes, paginated response shape, plugin auto-discovery).
- test_security_config.py: 7 tests pinning ProductionConfig.validate
  failure modes (missing/dev SECRET_KEY, missing JWT_SECRET_KEY,
  missing DATABASE_URL, wildcard CORS, empty CORS) and one happy-path.

Production hardening (shopdb/config.py, shopdb/__init__.py):
- ProductionConfig.validate() raises ConfigError on missing or
  insecure SECRET_KEY, JWT_SECRET_KEY, DATABASE_URL, CORS_ORIGINS.
  No silent fallback to dev defaults in production.
- create_app invokes validate() when config_name == 'production'.
- CORS_ORIGINS default no longer wildcard; defaults to localhost
  Vite dev origin.
- Drop os.path.exists probe in serve_frontend (path-traversal risk
  surface). send_from_directory handles safe-join + 404 itself.
- Replace User.query.get with db.session.get (SQLAlchemy 2.0 API).

TestingConfig (shopdb/config.py):
- Add StaticPool + check_same_thread connect_args so SQLite in-memory
  is shared across the test session.

Index dedup (plugins/printers/models/printer_extension.py):
- Rename idx_printer_windowsname -> idx_printerdata_windowsname.
  Two model classes (Printer, PrinterData) declared the same index
  name; SQLite enforces global index uniqueness even across tables.
  Per CONTRIBUTING.md naming convention, indexes follow
  idx_<table>_<column>.

Dependency pinning (requirements.in, requirements.txt):
- requirements.in holds the loose source pins (the human-edited file).
- requirements.txt is now a uv-compiled lockfile (every transitive
  dep pinned to an exact version). Reproducible builds. Run
  `uv pip compile requirements.in -o requirements.txt` to refresh.

Test count: 0 -> 15 passing. All naming/style checks still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:48:19 -04:00