Commit Graph

11 Commits

Author SHA1 Message Date
cproudlock
e005d1846a docs: wiki staleness sweep (Fable-orchestrated Opus audit)
Audited all 40 docs/ against the live codebase; fixed factual staleness in 23,
14 were clean. Highlights (all verified against code):
- equipment -> machines (ADR-011 rename) in INSTALL/DEPLOY-WINDOWS-IIS,
  PLUGIN-GUIDE, GE-ENFORCE, ROADMAP.
- Versions refreshed: contract 0.10.0 -> 0.13.0, product 0.5.0 -> 0.7.0, plus
  plugin example core_version pins.
- Bundled set corrected to the current 13 (PLUGINS.md 7 -> 13 rows; DEPLOY
  eleven -> thirteen).
- Per-plugin Alembic chain workflow (ADR-008) replacing stale core-chain steps
  in PLUGIN-QUICKSTART / BACKUP-RESTORE; deploy adds plugin upgrade-all.
- Frontend plugin staging (ADR-010) replacing 'no frontend plugin system yet'
  in PLUGIN-GUIDE; view/route paths repointed to plugins/<name>/frontend/.
- Corrected file paths (MapView.vue, manifest_schema.json), CLI (shelf-list),
  API gating (GET /api/plugins is optional-jwt), WJF 15 -> 16 stages, and
  retired Collector/PC-Types settings pages (ADR-012).
- ge-enforce proposal marked ACCEPTED/built.
2026-07-19 12:54:53 -04:00
cproudlock
efb879d44a Docs audit fixes: kiosk code drift, PowerShell chains, broken links, leaks
Some checks failed
CI / backend (push) Successful in 1m45s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
From the Fable/Opus documentation audit (8 confirmed + verified
lab-drift the run's session limit had cut short):
- HIGH: the lab's kiosk _kiosk_find_item block showed the pre-stage-17
  row-id resolver as current; replace with the shipped gagelabtag /
  numeric-tail resolver, fix the stale 'resolved by row id' prose and
  the 'stage-7 code is corrected' note.
- MED: the badge _external_lookup block used dict-only row access that
  breaks on a tuple cursor; use the tuple-or-dict form shipped. Split
  '&&' command chains (fail in PowerShell 5.1) in the lab.
- LOW/link: the Windows note's [DEVELOPMENT-SETUP] link dropped the .md
  and 404'd in four docs; fix. Correct the stage-6a->16a comment and
  the lab-stage tag range (..16 -> ..17).
- Leaks: drop /home/camp path from ADR-006, the internal gitea host
  from PLUGINS.md.
- Windows: add an mklink junction note for the external-plugin symlink
  dev loop.
- CI: prime root to mysql_native_password so pymysql connects to the
  MySQL 8 service without the cryptography package (and its kit wheel).
2026-07-17 18:05:47 -04:00
cproudlock
f77f0a8d90 Add GitHub Actions CI + Windows notes on the developer docs
Some checks failed
CI / naming (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / migrations-mysql (push) Has been cancelled
CI / backend (push) Has been cancelled
GitHub had no CI, so naming/tests/build were unenforced on the public
mirror. Add .github/workflows/ci.yml mirroring the internal pipeline:
backend pytest, the naming gate, frontend vitest+build, and the
migrations-mysql job that proves a fresh flask db upgrade + every
plugin chain on utf8mb4 MySQL 8 is idempotent. Flip the dev-setup CI
note to reflect it. Add an identical Windows/VS Code convention note to
the four developer docs (venv\Scripts vs venv/bin, $env: vs export,
pointer to DEVELOPMENT-SETUP).
2026-07-17 15:12:36 -04:00
cproudlock
7dfbe7bf8a Add the get_permissions plugin hook (contract 0.10.0)
All checks were successful
CI / backend (push) Successful in 1m20s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Plugins declare their own RBAC permissions instead of core accumulating
them: 36 permissions moved out of the core catalog into the 9 owning
plugins (core keeps the 19 its own blueprints enforce). The catalog is
resolved dynamically (core + enabled plugins) and feeds the roles grid,
the token scope picker and ceiling, and flask seed permissions;
installing or enabling a plugin seeds its permissions automatically. A
disabled plugin drops out of the assignable catalog while existing role
links keep working. New plugins - bundled or external - now bring their
permissions with zero core edits.

781 tests pass; live-verified with a machines.edit-scoped token.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 09:29:55 -04:00
cproudlock
24f67d6ac5 Accept + implement ADR-010 frontend plugin hooks (contract 0.7.0)
Some checks failed
CI / backend (push) Failing after 1m2s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Four data-only hooks on BasePlugin (get_settings_cards,
get_asset_panels, get_map_overlays, get_asset_presentation) with a
GET-only /api/pluginui consumer surface copying the dashboard-widgets
semantics. Pilots: warranty declares its asset panel; measuringtools
supplies its settings card, presentation, and calibration overlay -
the last hardcoded settings-nav entry is now hook-sourced. Generic
renderers for panels/overlays/presentation deferred per the ADR's
incremental adoption plan (documented in CONTRACT-STABILITY.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:38:32 -04:00
cproudlock
529b9f2fed Ship plugin framework shore-up: frontend scaffold, sister-site adoption kit
All checks were successful
CI / backend (push) Successful in 24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
- flask plugin new now scaffolds the frontend too: List/Detail/Form
  views on the global styles, a gated route module (ADR-009), and an
  api-client snippet emitted into the plugin dir. Views are written
  before the route file so a partially generated plugin cannot 500 the
  dev server.
- docs/PLUGIN-EXTERNAL-REPO.md + scripts/test-external-plugin.sh: how a
  sister site develops a plugin in its own repo and runs the framework
  contract tests in CI against a pinned framework ref (script verified
  to fail on a broken core_version pin).
- docs/CONTRACT-STABILITY.md: settled vs churning contract surface and
  the provisional 1.0 criteria.
- CLAUDE.md active-state refresh (contract 0.6.0, 11 plugins, 340
  tests, measuringtools done).

Known limitation documented: Path.rglob does not descend symlinks, so
the import-surface contract test skips symlinked external plugins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:30:03 -04:00
cproudlock
94f852a1c8 Add frontend integration checklist and docs-drift guard
All checks were successful
CI / backend (push) Successful in 24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
PLUGIN-QUICKSTART Step 7 is now a concrete 7-item checklist (view
conventions, route auto-discovery + ADR-009 gating meta, api client
shape, nav/report hooks, settings auto-nesting, verification).

New tests/test_docs_contract.py introspects BasePlugin and fails CI when
a public hook is missing from PLUGIN-HOOKS.md or the documented contract
version drifts - it immediately caught two undocumented hooks
(get_provisioning_note, get_config_schema), now documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:14:25 -04:00
cproudlock
22e623c1f6 Plugin framework maturation, reports overhaul, theming, and USB frontend repair
Framework:
- Per-plugin Alembic migration chains (ADR-008): every bundled plugin
  carries its own chain with a stamp-only anchor at the ownership cutover;
  new plugin schema lands in plugins/<name>/migrations/, never the core
  chain. Deploys add flask plugin upgrade-all. Fixed a latent bug in the
  shared alembic template (engine URL resolution) and taught the metadata
  filter to include FK-referenced core tables.
- Frontend plugin route gating (ADR-009): plugin routes carry meta.plugin;
  a disabled plugin's pages redirect to the dashboard via a cached,
  fail-open check against the new public GET /api/plugins/enabled.
- get_reports() plugin hook (contract 0.5.0 -> 0.6.0): plugins contribute
  report cards; warranty and toner cards moved off the hardcoded list.

Reports:
- Hub grouped by category with search; inline reports render at the top,
  are URL-backed (?report=id, back-button and deep links work), expose
  their server-side filter params as controls, and export CSV. Warranty
  and Toner pages gained CSV export.
- Deleted the dead legacy Warranty Status report (always-zero buckets
  from a retired column).

Theming and fonts:
- Inter (variable) bundled locally via @fontsource, replacing the Google
  Fonts Roboto import - air-gapped installs now render correctly; tables
  use tabular numerals.
- Optional brand_primary_dark_color, brand_accent_color,
  brand_sidebar_color settings applied to CSS vars at bootstrap.

USB frontend repair (views were reading a dead legacy shape):
- List/detail/form and the employee profile USB panels remapped to the
  real API shape (device_id/device_desc/checkinoutlog); employee panels
  now use /usb/checkouts endpoints; external-mode /usb/checkouts/active
  honors the badge filter; dead client methods pruned.

Also: warranties list page no longer requires login (matches app
convention); collector doc rewritten with a GE-Enforce integration guide
and paste-ready PowerShell reporter; ADR index and CHANGELOG updated.

Verified: 323 tests pass, naming/style green, frontend builds, plugin
migration dry-run green on scratch MySQL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:01:47 -04:00
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