Commit Graph

3 Commits

Author SHA1 Message Date
cproudlock
a515c28e3b Decouple core from plugins; wire widgets hook; drop dead search hook
Architectural pass from the skill review ("plugin is the product" boundary).

Core no longer imports plugin models at module load (was a hard import-time
dependency that broke core if the computers plugin was absent/disabled):
- collector.py, applications.py, reports.py: lazy + guarded imports of the
  computers plugin models. Endpoints that need install-tracking now return 503
  when the plugin is absent instead of failing at import.

Search honors runtime enable/disable:
- search.py: _require_enabled(name) raises ImportError for a disabled plugin,
  so each plugin-scoped block skips it (a disabled plugin's rows leave search).
- Replace hardcoded root/rootpassword employee-DB connection in _search_employees
  with the shared env-backed employee_connection helper.

Plugin hooks (integrating-plugin-hooks: every hook needs a consumer):
- get_dashboard_widgets: add the consumer GET /api/dashboard/widgets (5 plugins
  already implemented the hook; it had none). Skips disabled, isolates in prod.
- get_searchable_fields: REMOVED. Zero plugins implemented it and there was no
  consumer; global search is a core concern over the asset model. Contract
  reduction, __contract_version__ 0.3.0 -> 0.4.0.

Docs/contract: PLUGIN-HOOKS.md (widgets consumer note, searchable-fields removal,
0.4.0), PLUGIN-QUICKSTART.md, ADR-001 hook list. Tests: widgets endpoint
aggregate + disabled-skip; contract tests for the removed/added hooks.

151 tests pass, naming/style green, app boots all 6 plugins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:00:10 -04:00
cproudlock
06c16bde8a Phase 5: Alembic baseline, per-site deploy, ADRs to docs/adr
Migration runner ready and a sister site can deploy from a clean
checkout with one .env file.

ADRs relocated (migrations/adr/ -> docs/adr/):
- migrations/ is now Alembic territory, not docs.
- All cross-references updated: CLAUDE.md, docs/PLUGIN-HOOKS.md,
  docs/PLUGIN-QUICKSTART.md.

Alembic initialized (migrations/):
- env.py, script.py.mako, alembic.ini copied from Flask-Migrate
  templates so `flask db migrate` and `flask db upgrade` work without
  a one-time `flask db init` (which would clash with the existing
  migrations/ directory).
- Baseline migration generated via autogenerate, captures all 47
  tables (core models + 6 plugins) as the upgrade target. Ready for
  per-site `flask db upgrade` from an empty schema.

Deploy artifacts:
- Dockerfile: python:3.12-slim base, gunicorn server, non-root user,
  healthcheck against /api/auth/login. Single image bundles all six
  plugins; sites enable via `flask plugin install <name>`.
- docker-compose.yml: MySQL 8 + API container, healthcheck-gated
  startup, env-driven secrets that fail loud on missing values
  (`${SECRET_KEY:?}` form).
- .env.example: full env-var inventory with comments. Calls out
  required vs optional. Matches what ProductionConfig.validate
  enforces.

docs/DEPLOY.md:
- Step-by-step per-site runbook: clone, configure .env, bring up
  stack, run migrations, seed reference data, install plugins,
  create admin, front with TLS, backups, updates.
- Common-issues table.
- Cross-links to ADR-004 (per-site rationale), ADR-003 (plugin
  distribution), and the config source.

Skills:
- migrating-asset-schema: Alembic + one-shot data migration policy.
  Rules: additive first, renames are three steps, destructive ops
  need rollback, equipment migration filter per ADR-001 + ADR-005.
- hardening-flask-config: production validation, CORS allowlist
  policy, JWT cookie hardening, per-site deploy isolation per ADR-004.

CLAUDE.md updated to reflect the post-Phase-5 state. No tests added
this commit; the Alembic baseline is exercised by the existing
db.create_all-based test suite (tests do not touch the migration
runner; that's by design until per-plugin migrations land).

Test count unchanged: 101 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:56:19 -04:00
cproudlock
5de1fbe8a8 Phase 4: plugin scaffolding (flask plugin new) with canary tests
Lowers the barrier for sister sites to build their own plugins.
Generated output satisfies the framework contract out of the box.

CLI command (shopdb/plugins/cli.py):
- `flask plugin new <name> --description "..."` generates a plugin
  skeleton under plugins/<name>/. Validates the name against
  CONTRIBUTING.md rules (lowercase letters/digits only, no
  underscores or hyphens, not in the reserved list) and refuses to
  overwrite existing plugins unless --overwrite is passed.
- Output prints the next steps (install, migrate, test).

Scaffolder (shopdb/plugins/scaffolder.py):
- validate_name: enforces the naming rules
- pascal_case: lowercase-to-PascalCase for class names
- scaffold_plugin: copies templates with string.Template
  substitution. Three placeholders: $name, $Name, $description.
  Files with `model.py` in the path get renamed to <name>.py.

Templates (shopdb/plugins/templates/):
- manifest.json.tmpl: name, version 0.1.0, description, core_version
  range >=0.1.0,<1.0.0 (broad enough to survive minor framework bumps)
- plugin.py.tmpl: <Name>Plugin class extending BasePlugin with all
  required hooks implemented (meta from manifest, get_blueprint
  returning the bp, get_models returning the example model). Includes
  on_install hook that seeds the AssetType row.
- models/__init__.py.tmpl + models/model.py.tmpl: Asset extension
  table keyed by assetid with one example field. TODO comment marks
  it as a placeholder.
- api/__init__.py.tmpl + api/routes.py.tmpl: Blueprint with list and
  detail endpoints using the framework's pagination + response helpers.
- schemas/__init__.py.tmpl: marshmallow schema stub.
- tests/__init__.py.tmpl + tests/test_plugin.py.tmpl: smoke tests
  asserting plugin loads, get_blueprint returns Blueprint, get_models
  returns at least one model.
- README.md.tmpl: one-pager for plugin authors with common edits and
  next-step references.

Canary tests (tests/test_plugin_scaffold.py):
- 14 tests asserting the scaffold output passes contract checks.
- Validates name rules (lowercase, reserved, hyphens, digits, etc.)
- Verifies all expected files generated, manifest fields present.
- Loads the generated plugin via PluginLoader (spec_from_file_location
  bypasses the real `plugins` package shadowing).
- Asserts subclasses BasePlugin, get_blueprint returns Blueprint,
  get_models returns model with __tablename__.
- Module-scoped fixture; cleans up sys.modules + SQLAlchemy metadata
  on teardown to avoid cross-test contamination.

Quickstart docs (docs/PLUGIN-QUICKSTART.md):
- 30-minute walkthrough: scaffold -> edit model -> add routes ->
  install -> verify -> add hooks. Cross-links to PLUGIN-HOOKS.md and
  the ADRs. Includes common-errors table.

Test count: 87 -> 101 passing.

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