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).
This commit is contained in:
cproudlock
2026-07-17 18:05:47 -04:00
parent aba588cc07
commit efb879d44a
7 changed files with 53 additions and 31 deletions

View File

@@ -75,10 +75,16 @@ jobs:
python-version: '3.12' python-version: '3.12'
cache: pip cache: pip
- run: pip install -r requirements.txt - run: pip install -r requirements.txt
- name: Force utf8mb4 on the CI database - name: Prime the CI database (utf8mb4 + native auth for pymysql)
# MySQL 8 defaults root to caching_sha2_password, which pymysql can
# only speak with the 'cryptography' package. Rather than add that
# dependency (and its offline wheel), switch root to native auth here
# via the mysql CLI, so the app's pymysql connections work as-is.
run: | run: |
mysql -h 127.0.0.1 -uroot -proot -e \ mysql -h 127.0.0.1 -uroot -proot -e \
"ALTER DATABASE shopdb_ci CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" "ALTER DATABASE shopdb_ci CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; \
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; \
FLUSH PRIVILEGES;"
- name: Fresh core upgrade from empty - name: Fresh core upgrade from empty
run: flask db upgrade run: flask db upgrade
- name: Install every bundled plugin (runs its chain) - name: Install every bundled plugin (runs its chain)

View File

@@ -10,7 +10,7 @@ is required for v1 (pip distribution is deferred to v2 per ADR-003).
> **Windows / VS Code:** command examples use the Linux venv path > **Windows / VS Code:** command examples use the Linux venv path
> `venv/bin/python`; on Windows use `venv\Scripts\python` and > `venv/bin/python`; on Windows use `venv\Scripts\python` and
> `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding:
> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). > [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md).
If you have not written a plugin before, start with If you have not written a plugin before, start with
@@ -77,6 +77,11 @@ git clone https://github.com/ge-aero/wjsf-shipping.git
cd shopdb-flask cd shopdb-flask
ln -s ../../wjsf-shipping plugins/shipping ln -s ../../wjsf-shipping plugins/shipping
# (use an absolute path if you prefer: ln -s "$(pwd)/../wjsf-shipping" plugins/shipping) # (use an absolute path if you prefer: ln -s "$(pwd)/../wjsf-shipping" plugins/shipping)
#
# Windows: use a directory junction instead of ln -s. In an ADMIN prompt
# (or with Developer Mode on) from the shopdb-flask dir:
# mklink /D plugins\shipping ..\..\wjsf-shipping
# The plugin loader treats a junction the same as a real directory.
# 3. Set up the framework as usual. # 3. Set up the framework as usual.
python3 -m venv venv python3 -m venv venv

View File

@@ -8,7 +8,7 @@ way it does by walking the shipped code of the exemplar plugin.
> **Windows / VS Code:** command examples use the Linux venv path > **Windows / VS Code:** command examples use the Linux venv path
> `venv/bin/python`; on Windows use `venv\Scripts\python` and > `venv/bin/python`; on Windows use `venv\Scripts\python` and
> `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding:
> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). > [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md).
`measuringtools` was chosen as the exemplar on purpose. It is the first plugin `measuringtools` was chosen as the exemplar on purpose. It is the first plugin
built after the framework matured (ADR-005 scoped it; ADR-008 changed how plugin built after the framework matured (ADR-005 scoped it; ADR-008 changed how plugin

View File

@@ -17,7 +17,7 @@ decision records: `docs/proposals/printedparts-plugin.md`.
> **Windows / VS Code:** command examples below use the Linux venv path > **Windows / VS Code:** command examples below use the Linux venv path
> `venv/bin/python`; on Windows use `venv\Scripts\python` and > `venv/bin/python`; on Windows use `venv\Scripts\python` and
> `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding:
> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). > [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md).
Know before you start Know before you start
@@ -262,7 +262,7 @@ def downgrade():
See it work: See it work:
``` ```
flask plugin install printedparts && flask plugin enable printedparts flask plugin install printedparts; flask plugin enable printedparts
mysql> SHOW TABLES LIKE 'printed%'; -- both tables mysql> SHOW TABLES LIKE 'printed%'; -- both tables
mysql> SELECT * FROM alembic_version_printedparts; -- printedparts0001baseline mysql> SELECT * FROM alembic_version_printedparts; -- printedparts0001baseline
flask plugin upgrade-all -- printedparts: ok flask plugin upgrade-all -- printedparts: ok
@@ -299,7 +299,7 @@ printedparts_bp = Blueprint('printedparts', __name__)
@printedparts_bp.route('/items', methods=['GET']) @printedparts_bp.route('/items', methods=['GET'])
@jwt_required(optional=True) # stage 6a tightens this to view-gated @jwt_required(optional=True) # stage 16a tightens this to view-gated
def list_items(): def list_items():
"""List printed items, paginated; search + low-stock filter.""" """List printed items, paginated; search + low-stock filter."""
page, per_page = get_pagination_params(request) page, per_page = get_pagination_params(request)
@@ -629,10 +629,11 @@ def _external_lookup(kind, digits):
f'WHERE {column} = %s', (digits,)) f'WHERE {column} = %s', (digits,))
row = cursor.fetchone() row = cursor.fetchone()
if row: if row:
sso = str(row['SSO']) # pymysql may return a tuple or a dict cursor - handle both.
name = f"{(row['First_Name'] or '').strip()} " \ sso = str(row[0] if not isinstance(row, dict) else row['SSO'])
f"{(row['Last_Name'] or '').strip()}".strip() first = row[1] if not isinstance(row, dict) else row['First_Name']
return sso, name last = row[2] if not isinstance(row, dict) else row['Last_Name']
return sso, f"{(first or '').strip()} {(last or '').strip()}".strip()
except Exception: except Exception:
logger.exception('HR directory lookup failed for %s %s', kind, digits) logger.exception('HR directory lookup failed for %s %s', kind, digits)
finally: finally:
@@ -798,17 +799,25 @@ catch you (below).
def _kiosk_find_item(itemcode): def _kiosk_find_item(itemcode):
"""Resolve a scanned or typed code to an active item. """Resolve a scanned or typed code to an active item.
Accepts the full code (WJRP0042) or bare digits from the touch keypad - Matches the internal code OR the gage-lab tag exactly; bare keypad
the digits in a minted code ARE the row id, so id lookup keeps working digits match the numeric tail of EITHER identifier, and only when
even for labels printed under an older prefix.""" exactly one active item matches (see stage 17)."""
itemcode = (itemcode or '').strip() scanned = (itemcode or '').strip().upper()
item = PrintedItem.query.filter( item = PrintedItem.query.filter(
PrintedItem.itemcode == itemcode, or_(PrintedItem.itemcode == scanned,
PrintedItem.gagelabtag == scanned),
PrintedItem.isactive == True).first() PrintedItem.isactive == True).first()
if not item and itemcode.isdigit(): if not item and scanned.isdigit():
candidate = db.session.get(PrintedItem, int(itemcode)) wanted = int(scanned)
if candidate and candidate.isactive: matches = []
item = candidate for candidate in PrintedItem.query.filter_by(isactive=True).all():
for value in (candidate.itemcode, candidate.gagelabtag):
tail = ''.join(ch for ch in (value or '') if ch.isdigit())
if tail and int(tail) == wanted:
matches.append(candidate)
break
if len(matches) == 1:
item = matches[0]
return item return item
@@ -920,7 +929,8 @@ function onWedgeEnter() {
``` ```
Manual fallbacks use the TouchKeypad: badge entry is digits (an SSO), and Manual fallbacks use the TouchKeypad: badge entry is digits (an SSO), and
item entry is bare digits resolved by row id server-side - no alphanumeric item entry is bare digits matched server-side against the numeric tail of
either the internal code or the gage-lab tag (unique match only) - no alphanumeric
on-screen keyboard needed. Success screen auto-resets after a few seconds. on-screen keyboard needed. Success screen auto-resets after a few seconds.
Full component (~250 lines) at the tag. Full component (~250 lines) at the tag.
@@ -1093,7 +1103,7 @@ failure. Summaries here; complete diffs at the tags.
hit the deny policy. Lesson: anything resolving PEOPLE must honor the hit the deny policy. Lesson: anything resolving PEOPLE must honor the
site's directory mode (the stage-5 code above is the corrected version). site's directory mode (the stage-5 code above is the corrected version).
- **16 - touchscreen findings** (`lab-stage-16`): the focus-steal guard and - **16 - touchscreen findings** (`lab-stage-16`): the focus-steal guard and
keypad-driven manual entry (the stage-7 code above is the corrected keypad-driven manual entry (the kiosk resolver above is the stage-17
version). version).
- **17 - the gage-lab asset tag** (`lab-stage-17`): the field team assigns - **17 - the gage-lab asset tag** (`lab-stage-17`): the field team assigns
@@ -1134,11 +1144,12 @@ for a contributor:
2. **Build in stage-sized commits** exactly as this lab does - each commit a 2. **Build in stage-sized commits** exactly as this lab does - each commit a
working checkpoint with its tests. Subject line: short, plain English, working checkpoint with its tests. Subject line: short, plain English,
present tense ("printedparts stage 5: the ledger"); body says WHY. present tense ("printedparts stage 5: the ledger"); body says WHY.
3. **Before every push**, the same three gates CI runs: 3. **Before every push**, run the three gates CI runs (in VS Code: the
```bash **Check: naming + tests + build** task). By hand in PowerShell:
bash scripts/check-naming-and-style.sh ```powershell
venv/bin/python -m pytest tests/ -q venv\Scripts\python -m pytest tests/ -q
cd frontend && npx vitest run && npm run build cd frontend; npx vitest run; npm run build; cd ..
bash scripts/check-naming-and-style.sh # naming - runs via Git Bash
``` ```
4. **Push your branch and open a Pull Request** against `main`: 4. **Push your branch and open a Pull Request** against `main`:
```bash ```bash
@@ -1179,4 +1190,4 @@ for a contributor:
| List/Detail master templates | `PrintersList.vue`, `PrinterDetail.vue` | | List/Detail master templates | `PrintersList.vue`, `PrinterDetail.vue` |
| Reports hook + CSV | `plugins/warranty/` + `shopdb/core/api/reports.py` | | Reports hook + CSV | `plugins/warranty/` + `shopdb/core/api/reports.py` |
| Permissions declaration | `plugins/usb/plugin.py::get_permissions` | | Permissions declaration | `plugins/usb/plugin.py::get_permissions` |
| The finished plugin itself | branch `feat/printedparts-plugin`, tags `lab-stage-01..16` | | The finished plugin itself | branch `feat/printedparts-plugin`, tags `lab-stage-01..17` |

View File

@@ -7,7 +7,7 @@ For the full hook reference, see [PLUGIN-HOOKS.md](PLUGIN-HOOKS.md).
> **Windows / VS Code:** command examples below use the Linux venv path > **Windows / VS Code:** command examples below use the Linux venv path
> `venv/bin/python`; on Windows use `venv\Scripts\python` and > `venv/bin/python`; on Windows use `venv\Scripts\python` and
> `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding:
> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). > [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md).
For the architectural decisions behind the contract, see [docs/adr/](../docs/adr/). For the architectural decisions behind the contract, see [docs/adr/](../docs/adr/).

View File

@@ -62,7 +62,7 @@ which revisions each plugin has applied in `migrations_applied`.
For sister-site plugins (per [ADR-003](adr/ADR-003-plugin-distribution.md)): For sister-site plugins (per [ADR-003](adr/ADR-003-plugin-distribution.md)):
- Plugin lives in its own git repo: `gitea.proudtech.net/<your-site>/<pluginname>` - Plugin lives in its own git repo: `<git-host>/<your-site>/<pluginname>`
- Adopting site clones or symlinks into their `<repo>/plugins/<name>/` - Adopting site clones or symlinks into their `<repo>/plugins/<name>/`
- Plugin manifest declares `core_version` range matching the framework version they target - Plugin manifest declares `core_version` range matching the framework version they target
- Plugin readme explains: what it tracks, who maintains it, where to file issues - Plugin readme explains: what it tracks, who maintains it, where to file issues

View File

@@ -145,4 +145,4 @@ Migration path:
- `shopdb/plugins/base.py` (`get_collector_schema` + `apply_collector_payload` hooks) - `shopdb/plugins/base.py` (`get_collector_schema` + `apply_collector_payload` hooks)
- ADR-001 (asset model the collectors target) - ADR-001 (asset model the collectors target)
- ADR-002 (collector schema is part of plugin contract; changes to the hook signature are major bumps) - ADR-002 (collector schema is part of plugin contract; changes to the hook signature are major bumps)
- The PXE project (`/home/camp/projects/pxe/`) which feeds the computers collector - The PXE project (the PXE imaging project) which feeds the computers collector