Commit Graph

172 Commits

Author SHA1 Message Date
cproudlock
012718f0fd Import-API: preserve app-version timestamps in import mode
Some checks failed
CI / backend (push) Successful in 1m40s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
POST /api/applications/{id}/versions now calls apply_import_timestamps so an
imported version's original dateadded/releasedate survives (was skipped, unlike
the app + install endpoints). No-op outside import mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:12:03 -04:00
cproudlock
be1ea29403 Import-API hardening: network device IP endpoint + communicationtypes seeder
The legacy-import surface (docs/IMPORT-API.md) is the schema-agnostic contract
every adopting site targets; these close two gaps found while mapping the WJ
classic import.

Network device IP: POST/PUT /api/network now accept an `ipaddress` and
materialize a primary Communication (mirroring the printer route), and GET
(list + detail + create/update result) surface it. Previously a network
device's IP - which lives in the communications table, not on the extension -
had no HTTP import path at all.

communicationtypes seed: `flask seed reference-data` now seeds the eight
canonical communication types (IP/Serial/Network_Interface/USB/Parallel/VNC/
FTP/DNC), which IMPORT-API.md already documents as a prerequisite. The IP type
must exist before any asset import so printer/network routes can attach an IP.
There is no CRUD endpoint for these, so seeding is the only path.

Tests: network create/update IP upsert + GET surfacing + seed creates IP type.
204 targeted tests pass; naming + pyflakes green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:11:01 -04:00
cproudlock
1c6c7ba14b DB review fixes: drop redundant indexes + dead column, add CI MySQL-upgrade job
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
From the database review (verdict: sound-with-minor-issues). Applies the
actionable findings.

Redundant indexes: five non-unique secondary indexes duplicated a named idx_*
or a unique index on the same column - ix_communications_assetid,
ix_computers_hostname, ix_networkdevices_hostname, ix_printers_hostname (each
shadowing an idx_*), and idx_usb_serial (shadowing the serialnumber unique
index). Removed the redundant index source from the models (column index=True /
the extra db.Index) and added core migration 7d25 dropping the live duplicates.
The unique ix_*_assetid indexes are kept (they enforce assetid uniqueness).

Dead column: usbcheckouts.machineid was a NOT NULL soft-ref to the retired
machines table storing sentinel 0 (ADR-001). Dropped from the model + the
machineid=0 literal in selfhosted checkout; usb plugin migration 0002 drops it
live (downgrade restores it default 0).

Index: notifications.businessunitid (filtered by the shopfloor feed) was
unindexed; added index=True + notifications migration 0002.

CI: new migrations-mysql job proves the real multi-site deploy path - fresh
`flask db upgrade` + per-plugin install on utf8mb4 MySQL from empty, asserting
table count + charset and a clean second-run no-op. The pytest suite only
exercises SQLite create_all(), so a regression in the Alembic chain on MySQL
would otherwise ship undetected.

Verified: fresh core upgrade on a scratch utf8mb4 MySQL builds clean + no-op on
rerun (redundant indexes absent, unique assetid kept); plugin migrations applied
+ verified on the dev DB (machineid gone, bu index present). 953 backend tests
pass; naming + pyflakes green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:29:45 -04:00
cproudlock
9c8b2c9c9e DB review safe-fix: naive-UTC timestamp defaults (drop db.func.now)
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / frontend (push) Has been cancelled
DB review found four DateTime columns defaulting to db.func.now() (MySQL
session-timezone wall clock) while the rest of the schema stores naive UTC, so
one schema mixed two clocks and to_dict() labelled the local values UTC with a
'Z' suffix. Switch application.dateadded, computers.installeddate,
knowledgebase.lastupdated (default + onupdate), and slides.uploadeddate to the
module-level naive-UTC _utcnow callable already used elsewhere (apitoken.py).
ORM-side default only - no column-type change, no data migration; affects
new/updated rows.

Targeted tests pass (154); naming + pyflakes green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:09:29 -04:00
cproudlock
cd02cd20f4 Frontend naming + CSS-variable cleanups (review low)
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / frontend (push) Has been cancelled
Naming convention (LOCKED): rename the ManifestEditor simulate state sim ->
simulateInputs / simResult -> simulateResult (+ .sim-result CSS class) - 'sim'
was banned standalone shorthand. Rename AssetRelationships props assetId ->
assetid and machineNumber -> machinenumber so a prop holding a DB field value
mirrors it verbatim; updated the five detail-page call sites (:assetid=).

CSS variables: SearchResults per-domain badge palette moved into CSS variables
on the container; the duplicated prefers-color-scheme dark block collapses to a
single set of variable overrides instead of restating all ten selectors.

frontend build green; vitest 49 pass; naming green; search badges + detail
relationships verified rendering with no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:48:50 -04:00
cproudlock
b99da362b5 Docs: refresh ROADMAP to current state (contract 0.11.0, phase 6 done)
Review flagged ROADMAP as six contract versions stale. Set opening version to
0.11.0; mark phase 6 (multi-site distribution) done and name the real last
milestone (legacy import + prod pilot); drop the three completed items
(per-plugin Alembic chains per ADR-008, local font bundling - Inter is bundled
via @fontsource, measuringtools plugin built); reframe the frontend item to the
part that actually remains (external plugin UI packaging - hooks + gating
already shipped via ADR-009/010); add ADR-007..012 to the decision-log pointers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:42:51 -04:00
cproudlock
ea3cca8954 Test coverage: plugin lifecycle, registry, lockout unlock, contract fleet, seed
Fills the review's highest-value coverage gaps in the framework's own core
feature.

New tests/test_plugin_lifecycle.py: manager enable/disable dependency guards
(beta depends on alpha - enable-beta-first refused, disable-alpha-while-beta-on
refused) via synthetic plugins; enable seeds a plugin's RBAC permissions
idempotently; registry disable-survives-reload, corrupt-file recovery, and the
equipment->machines rename migration; `flask seed settings` idempotency.

test_plugin_contract.py: BUNDLED_PLUGINS now covers all bundled plugins incl.
geenforce, measuringtools, warranty (was 9, contradicting the CLAUDE.md "all
bundled satisfy the contract" claim); the structural checks now run against them.

test_authz.py: account lockout auto-unlock path (expired lockeduntil -> correct
password logs in and clears the lock state), previously untested.

All new tests pass; naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:41:23 -04:00
cproudlock
d49baeb5fa Security closeout: settings public allowlist, audit.view gating, test-user guard
Settings exposure (review medium): GET /api/settings and /api/settings/<key>
now return the full table only to an authenticated principal. Unauthenticated
callers (kiosk dashboards, print pages, login screen, setup router) get just a
public allowlist - categories branding + map plus a named set (site_base_url,
facility_name, printer_hostname_template, contact_email_domain,
servicenow_enabled, setup_complete). A non-public single-key GET returns 404 so
existence is not confirmed. Secrets stay masked in both cases. Closes the
unauthenticated enumeration of smtp_host / employee_db_host / zabbix_url /
servicenow URLs. Allowlist mirrors the keys siteSettings.js + mapConfig.js +
setupState.js read before login.

audit.view (review low): the three audit-read routes (list, entity-history,
stats) were jwt_required only despite a defined-but-unwired audit.view
permission; now gated by it (seeded to admin), so a role-less member or unscoped
PAT can no longer read the cross-user audit trail.

flask seed test-user (review low): refuses outside DEBUG/TESTING - it creates
the well-known admin/admin123; production sites use `flask seed admin`.

Tests: unauthenticated allowlist + authed-full-masked + private-key-404, and
member-403 / admin-200 on audit routes. 336 authz tests pass; naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:36:19 -04:00
cproudlock
cd353b6432 Review safe-polish: docs accuracy, dead imports, no-emoji, geenforce robustness
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / frontend (push) Has been cancelled
From the full multi-agent review (0 high, 7 medium, 17 low findings). Applies
the mechanical, low-risk items; design/policy findings left for a decision.

Docs accuracy: CLAUDE.md contract 0.10.0 -> 0.11.0 and both stale Alembic head
citations -> 7d24_customfield_searchable / 31 migrations; Dockerfile bundled-
plugin comment fixed (drop nonexistent "equipment", add machines +
measuringtools, count eleven).

Style/naming (LOCKED rules): remove a CSS-escaped pushpin emoji before location
search results (no-emoji policy); rename ManifestEditor shareRoot -> shareroot
(variable mirrors the API field verbatim).

Dead code: remove confirmed-unused imports across ~20 modules (require_role/
require_permission scaffold residue, stray db/Vendor/Model/current_user/Optional/
error_response); drop unused build_scope import + a stale GEENFORCE_API_KEY
docstring clause in geenforce. Migration files left untouched.

Correctness: geenforce ingest robustness - record_enforcement_report now 400s
on a non-dict counts / non-list results instead of 500; _apply_app_link ignores
a non-numeric appid per its docstring instead of 500. Regression tests added.

Backend query.get sweep finished: auth.py refresh -> db.session.get (last one).

910 backend tests pass; pyflakes clean; naming green; frontend build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:02:43 -04:00
cproudlock
85a6ab8645 Setup wizard: plugin display names + GE-Enforce next-steps pointer
All checks were successful
CI / backend (push) Successful in 1m37s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Plugins list now carries a displayname (manifest display_name, else the
machine name title-cased). Adds display_name to the four whose title-case
was wrong: GE-Enforce, USB, Measuring Tools, Knowledge Base. The setup
wizard Features step and Settings > Plugins render it, so "Geenforce"/"Usb"
are gone.

Finish step shows a pointer when GE-Enforce is enabled: it still needs a
scoped service token (Settings > API Tokens) and a share export root
(GE-Enforce page) before the fleet uses it - operational config the wizard
does not collect.

frontend build green; naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 07:41:32 -04:00
cproudlock
d7b777a7a0 GE-Enforce editor: onboarding field guidance
All checks were successful
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
Per-method detection hint: a plain-language line explains what "already
correct" means for the selected detection method (Registry/File/FileVersion/
Hash/MarkerFile/ValueMatches/pnputil/Always), updating live as the author
picks one. Lives in entryForm.js (DETECTION_METHOD_HINTS + detectionMethodHint)
with 4 new vitest cases; the editor renders it under the dropdown.

Also: relative-path hint on Installer/Source (path under the scope payload
folder or an inline payload), an InUseCheck behavior hint, and refresh the
entryForm.js header now that the editor imports these helpers directly.

vitest 49 pass; frontend build green; naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 07:31:43 -04:00
cproudlock
456a44104b GE-Enforce polish: DDL-parity guard test, retire Collector PC Types page
All checks were successful
CI / backend (push) Successful in 1m40s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Add test_geenforce_ddl_parity to lock the manifest models against their
Alembic baseline (catches model/migration drift for a chain that is still
amendable pre-deploy).

Retire the "Collector PC Types" settings card: GE-Enforce scope
computertypeid supersedes the pctypemap editor UI (ADR-012). The collector
still reads pctype_mapping(), so the backend map stays; only the editor
surface is removed, with a deprecation note in pctypemap.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 07:25:01 -04:00
cproudlock
4e5b4228c1 GE-Enforce: compliance view, inline payload upload, frontend test harness
Fleet-install compliance for app-linked manifest entries: new service
compliance_for_scope + GET /geenforce/scopes/<id>/compliance count active
ComputerInstalledApp rows by curated appid (null-safe when computers plugin
absent). ManifestEditor gains a compliance panel. Curated appid stays shopdb
metadata and never enters manifest JSON, so behavioral parity is unaffected.

Inline manifest payloads: store_inline_payload (sha256, 1MB cap,
payloadsource='inline') + POST/GET /geenforce/entries/<id>/payload; editor
gains an upload control. Entry payload metadata surfaced in _entry_payload.

Frontend test harness: extract the editor's entry-form logic into pure
entryForm.js (buildEntryPayload, describeEntry, availableEntryTypes, scope
gates, ...) and cover it with 45 vitest tests. ManifestEditor now imports
those helpers, so the tests exercise the shipped code path (no duplication).

908 backend tests pass; vitest 45 pass; frontend build green; naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 07:24:51 -04:00
cproudlock
3355436fcd Add curated manifest-entry -> Application link (honest app tracking)
All checks were successful
CI / backend (push) Successful in 1m34s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
The honest replacement for the backed-out auto-seeding: instead of scraping
manifest labels into duplicate Application rows, an entry can be LINKED to an
existing catalog Application, cross-referencing what shopdb already tracks.

- Model: manifestentries.appid (nullable soft ref to core applications; in the
  0001 baseline). It is shopdb METADATA, deliberately NOT a manifest field - it
  never appears in the rendered manifest JSON, so enforcement + parity are
  unaffected (test asserts it stays out of the preview manifest).
- API: _entry_payload returns appid + resolved appname; create/update accept an
  optional appid (validated, unknown id ignored, null unlinks) via _apply_app_link;
  GET /geenforce/applications is the picker source (id + name).
- Editor: a "Tracked application (optional)" select in the entry modal, and the
  entry summary line notes the linked app ("...; tracked: eDNC").
- Foundation for a future desired-vs-observed compliance view.

889 tests green (incl. the link test + parity/migration unaffected); build +
naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 06:45:10 -04:00
cproudlock
810687953f ADR-012: GE-Enforce manifest ownership in shopdb (ACCEPTED)
All checks were successful
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Formalizes the design built this session: manifest as shopdb data (wide entries
table + entrytype discriminator, per-plugin Alembic chain), immutable published
snapshots + rollback, behavioral-parity gate, engine-as-source-of-truth filter
mirror, payload integrity separate from detection, observed-state reporting,
service-token auth (contract 0.11.0), client kit + provisioning-agnostic
Install-GEEnforce bootstrap (engine referenced not vendored), Milestone-1
export-to-share + staged cutover, and NO application auto-seeding (curated
linking instead). Indexed in ADR README + CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 06:37:40 -04:00
cproudlock
3b400d8cc4 Fix GE-Enforce client kit under PowerShell 7 (header-array coercion)
All checks were successful
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
Found by running the kit under pwsh 7 against the live API: Invoke-WebRequest
returns header values as string ARRAYS in PS7 (scalars in Windows PowerShell
5.1), so X-Manifest-Version came back as @('1') and [int] on it threw - report
build failed. The target scheduled task runs 5.1 (works), but the kit must be
robust under PS7 too (target preinstalls PowerShell 7). Coerce ETag and
X-Manifest-Version with @(...)[0], a clean scalar in both.

Validated end to end on Linux pwsh 7.6.3 against the dev API: fetch (200) ->
cache-304 -> report sent -> landed received=true/status=ok. All 4 client scripts
parse clean; PSScriptAnalyzer shows only cosmetic warnings (Write-Host in a CLI,
intentional log-guard catch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 06:23:56 -04:00
cproudlock
8dceb8812f Add GE-Enforce agent deployment: Install-GEEnforce.ps1 + deploy doc
All checks were successful
CI / backend (push) Successful in 1m36s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
Closes the "how do sites actually deploy GE-Enforce" gap (esp. OOBE-ppkg sites
without a PXE/WinPE step). Site-neutral + imaging-path independent.

- plugins/geenforce/client/Install-GEEnforce.ps1: a bootstrap that writes the
  PC's identity (C:\Enrollment\pc-type.txt is what determines the PC type; plus
  machine-number/cmm version/cmm id/site-config as needed), sets the shopdb
  BaseUrl + token in HKLM:\SOFTWARE\GE\ShopDB, deploys the client kit, optionally
  copies the engine from -EngineSource, and registers the SYSTEM scheduled task
  (at logon + every N min). Idempotent; fails loud (installer, not the fail-safe
  runtime). Engine is REFERENCED not vendored - it belongs to the GE-Enforce
  framework; the script warns if absent but still labels the PC.
- docs/GE-ENFORCE-DEPLOY.md: the deploy contract - the three things a PC needs
  (client, identity, credential), the identity table (what determines PC type,
  no auto-detection - the provisioner supplies it; shopdb cannot set it at
  imaging), and how to invoke per path (PXE step, OOBE ppkg via
  ProvisioningCommands, Intune, manual), the engine boundary, and verification.
- Cross-linked from docs/GE-ENFORCE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 23:02:54 -04:00
cproudlock
6e7d51de21 GE-Enforce editor: phase-aware form, scope summary, narrower entries table
All checks were successful
CI / backend (push) Successful in 1m37s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
- Phase-aware editing: a preinstall scope now only offers MSI/EXE types and
  Registry/File detection (the preinstall runner silently skips the rest), and
  the preinstall flags show only for a preinstall scope - so an author cannot
  pick an option that would do nothing.
- Per-scope summary line ('Installs PC-DMIS 2016, ...; 4 entries; runs after
  common') under the scope header.
- Entries table: dropped the redundant Detection + Filters columns (the
  plain-English entry line already conveys them), fixed-layout with sized button
  columns and stacked Up/Down - no more horizontal scroll.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:57:06 -04:00
cproudlock
e6ed47c533 Fix 7 factual errors in GE-Enforce doc (Fable fact-check vs real source)
All checks were successful
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Verified claim-by-claim against the engine/dispatcher/preinstall runner/manifests.
Corrections (3 were ship-blocking):

- Timeline (ship-blocking): identity files (pc-type/machine-number/cmm version/
  site-config) are written in WinPE at the PXE menu BEFORE the image boots, not
  during a post-imaging 'enrollment' step; preinstall already reads them. Added a
  step [0]; enrollment is now only Intune + Azure DSC credential provisioning.
- _CmmVersion (ship-blocking): a CMM bay with NO resolved version gets ALL
  PC-DMIS versions (legacy install-all), not none.
- machine-number 9999 (ship-blocking): the enforcement engine does not
  special-case 9999; it is a placeholder that won't match real bay gates (the
  9999-skip is status-write-back only).
- Preinstall runner implements only MSI/EXE + Registry/File detection, not the
  full matrix (that is runtime-only).
- Runtime processes up to three scopes: common, type, then optional type-subtype.
- pc-subtype.txt is legacy (no longer written at imaging since 2026-05-04).
- The collector ComputerType mapping lives at Settings > Collector PC Types, not
  the geenforce scope (scope computertypeid is a local reference field).
- FileVersion is a raw string compare; 4-part is convention, not engine-enforced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:47:59 -04:00
cproudlock
ab0c9a454b Make GE-Enforce editor more legible: plain-English entries + how-it-works panel
All checks were successful
CI / backend (push) Successful in 1m37s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
- Each manifest entry now shows a one-line intent summary under its name
  (e.g. 'Installs eDNC; reinstalls if not detected; 2 PC types') instead of only
  the raw Type/Detection/Filters columns - turns jargon into what it actually does.
- A collapsible 'How this works' panel at the top gives the mental model in four
  sentences (desired state + self-heal, entries + detection, targeting, doc link).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:45:50 -04:00
cproudlock
655fe1087f Add GE-Enforce guide: concepts, shopdb plugin, imaging-time integration
All checks were successful
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
docs/GE-ENFORCE.md - operator-facing guide grounded in the real engine +
manifests (Fable-verified analysis): how GE-Enforce works (preinstall vs runtime
phases, the enforce loop, manifest scopes/entries, self-heal detection, gates,
enrollment), WHEN it installs/takes over in the imaging timeline (preinstall at
imaging -> GE-Enforce laid down -> enrollment provisions creds -> runtime
enforcement from first logon), how the shopdb plugin manages it (Manifests
authoring + contextual targeting + simulate + publish/rollback + Export to Share
Milestone 1, Enforcement Reports), day-to-day IT tasks, and a reference index.
Complements GE-ENFORCE-CLIENT.md (client contract) and the proposal (plan).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:36:18 -04:00
cproudlock
378af32c1d Rebuild GE-Enforce editor: themed (Fable redesign) + semantics-aware targeting
All checks were successful
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
Replace the generic field-dump editor with the Fable-coordinated redesign
(stock .section-card / .settings-grid / .setting-row / .table-container /
badge / btn primitives, opaque global .modal on --bg-card-solid, proper
modal-header/body/footer), then layer the manifest semantics on top:

- CONTEXTUAL targeting: an entry only shows the gates its scope actually uses,
  instead of every gate on every entry. Data-driven + scope-aware -
  common/preinstall (fleet-wide) show PC types; CMM shows the version gate;
  a scope whose entries use machine numbers (collections) shows those; hostnames
  show when used. A short explainer states why (a per-type manifest already runs
  only on its own type), and "Show all targeting options" reveals everything.
  Hidden gates keep their value on save (no data loss).
- Preserves all functionality: type-switched payload blocks, detection reveal,
  structured InUseCheck rows (name/ExePath/timeout), LogFile, preinstall
  checkboxes, simulate, publish/versions/rollback, export. Entry modal widened
  to min(1100px,96vw).

EnforcementReports adopts the themed redesign (filters, card > table-container,
badge-mapped statuses, global modal) with the error handling kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:15:48 -04:00
cproudlock
3ac41c1556 Back out app auto-seeding; fix report-status + PCTypesStrict bugs (manifest review)
All checks were successful
CI / backend (push) Successful in 1m45s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
A deep Fable review of the real manifest corpus (READ-ONLY reference) showed the
manifests are an ENFORCEMENT PROGRAM, not an application inventory, and that
auto-seeding the Applications catalog from entry Type + Name was wrong:

- The catalog ALREADY tracks these apps from the classic-shopdb migration, with
  version histories (PC - DMIS, UDC x11 versions, eMX / eDNC, CLM, CSF, Oracle
  Database, FormTracePak). Seeding from manifest labels created DUPLICATES under
  different names (PC-DMIS 2016 vs PC - DMIS; eDNC (bundles NTLARS) vs eMX / eDNC;
  OpenText HostExplorer ShopFloor vs CSF). It also misclassified config drops
  (eMxInfo.txt) as apps and could never match a PC's reported ARP name.
So the seed-applications command + service are removed. Properly linking
manifest entries to the EXISTING catalog is a curated feature, not label-scraping.

Two REAL bugs the review found are fixed and kept:
- Report status (R4): every healthy cycle runs Always/no-detection scripts the
  engine counts as "installed", so keying self-heal off installed>0 marked the
  common scope selfhealed forever and made 'ok' unreachable. Status now derives
  from explicit per-entry self-heal flags only; the stored flag no longer infers
  from action=='installed'; the client kit doc reflects it.
- PCTypesStrict (R5): the runtime engine has no strict handling (preinstall
  runner only). filters.matches_pctype now applies strict only when phase ==
  'preinstall'; simulate + parity thread the scope phase through; the strict test
  uses a preinstall scope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:56:18 -04:00
cproudlock
fc1f56fec3 Widen GE-Enforce entry modal to min(1100px,96vw), 3-column form grid
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:26:54 -04:00
cproudlock
796007bcca Track manifest apps in the Applications catalog; make CMM gate contextual
All checks were successful
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
seed-applications: a flask geenforce seed-applications command + service that
reads the imaging-PC-type manifests and creates a core Application for every
installer entry (MSI/EXE/CMD/BAT), so shopdb tracks what GE-Enforce actually
deploys. Idempotent, deduped by appname; File/Registry/PS1/INF config entries
are skipped. Run against the West Jefferson reference: 27 apps tracked (PC-DMIS
2016/2019/2026, eDNC, Oracle Client, Adobe Reader, HostExplorer, the VC++ redist
matrix, Keyence VR-6000, PowerShell, Display Kiosk, ...). 2 tests.

Editor: the CMM version gate (_CmmVersion) now only shows for CMM scopes - it is
metrology-specific, so a printer/common entry form no longer carries the
irrelevant field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:21:49 -04:00
cproudlock
d9d40297b6 Fix transparent + cramped GE-Enforce modals
--bg-card is translucent (rgba .4) in dark mode - a glass effect for cards on
the page, but a floating modal rendered over content showed the page through it.
Modals now use --bg-card-solid (the opaque surface the shared Modal.vue uses),
with a border + shadow, and are wider (entry editor min(920px,94vw)) so the full
field set fits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:19:06 -04:00
cproudlock
24e38c6e75 Remove orphaned settings-dir GE-Enforce views (moved to geenforce/ section)
All checks were successful
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
The earlier move copied instead of moving, leaving unreachable duplicates under
views/settings/. The routed copies live in views/geenforce/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:14:00 -04:00
cproudlock
7089a61b50 Move GE-Enforce to its own top-level section; fix overflow + input theming
GE-Enforce is a large operational surface (manifest authoring + fleet
compliance), not a setting, and it was squished in the settings two-pane shell.
Promote it to a dedicated full-width top-level section:

- New sidebar entry "GE-Enforce" (plugin get_navigation_items, shield icon,
  auto-gated to the enabled plugin) instead of two Settings > Integrations cards.
- Tabbed shell GeEnforceLayout.vue (Manifests | Enforcement Reports) with
  full-width children under AppLayout, not the narrow settings rail.
- Views moved settings/ -> geenforce/ (ManifestEditor.vue, EnforcementReports.vue).

Theming + overflow fixes (the "chaotic / cut off / different inputs" report):
- Inputs/selects/textareas now match the stock settings look (border, radius,
  --bg, focus color) instead of browser defaults.
- No horizontal overflow: editor grid uses minmax(0,1fr) + min-width:0 on
  children, collapses to one column under 1000px; entry table and reports table
  scroll inside their own overflow-x containers; detail actions wrap.

Verified at 1280px: no page overflow, detail pane + tables fit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:12:57 -04:00
cproudlock
0dcd186820 Fix defects found in session review of GE-Enforce plugin
All checks were successful
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
Consolidated fixes from a three-dimension adversarial review.

Data-loss (HIGH): the manifest entry editor stripped fields the form did not
expose, because PUT /entries is a full reset-then-apply. The form now captures
everything - InUseCheck processes as structured name/ExePath/timeout rows (not
just names), LogFile, and the three preinstall flags as checkboxes; the dead
payload-source control (never wired) is removed. New regression test proves an
edit preserves ExePath/timeout/LogFile/PreEnrollment/PCTypesStrict.

Update-entry crash (found by that regression test): replacing an entry's
one-to-one InUseCheck (unique entryid) collided with the old row mid-flush ->
IntegrityError -> 400. update_entry now frees the old InUseCheck (delete+flush)
before populate re-inserts it.

Export truncation (MEDIUM): export_scope_to_share used a plain truncating open,
so a failed/partial write left the live on-share manifest (every PC reads it)
empty. Now writes a temp file in the same dir and os.replace() atomically.

Report dedup case bug (MEDIUM, confirmed by scratch test): the iscurrent demote
matched hostname case-sensitively while the read path uses ilike, so a PC
reporting different casing left two iscurrent rows and double-counted. Demote is
now case-insensitive; regression test added.

Simulator fidelity (MEDIUM): PCTypesStrict was captured but ignored by the
filter mirror, so the simulator wrongly matched a collections-only strict entry
to a nocollections PC via the shared Standard alias group. matches_pctype now
honors PCTypesStrict (disables alias expansion); test added.

Hardening: removed the dead/unscoped GEENFORCE_API_KEY env fallback (never wired
into config; tokens are the only path); create/update entry return 400 on a
duplicate Name instead of 500; parity now asserts scope-level Version/Site; a
new test guards real-manifest field lengths against column limits (the DB-free
parity harness can't see truncation); error handling added to the previously
unguarded editor + reports API calls.

Full suite green; naming + frontend build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:46:09 -04:00
cproudlock
d894f054ac Add GE-Enforce P4 client kit: fetch + report + shadow mode (reference)
All checks were successful
CI / backend (push) Successful in 1m42s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Client-side integration kit for sourcing manifests from shopdb and reporting
results back. Site-neutral reference a site adapts into its GE-Enforce.ps1; the
live dispatcher and engine are NOT touched (they are read-only reference under
projects/pxe). Only the manifest JSON source moves from a share file to shopdb,
plus a result report.

- plugins/geenforce/client/ShopdbEnforceClient.psm1: Sync-ShopdbManifest (GET
  with ETag -> local cache; falls back to last-known-good when shopdb is
  unreachable so a PC is never left unmanaged), Compare-ShopdbShadow (behavioral
  diff vs the on-share manifest), Send-ShopdbReport / New-ShopdbReport (best-
  effort POST /report), Get-ShopdbConfig (BaseUrl + token from
  HKLM:\SOFTWARE\GE\ShopDB).
- plugins/geenforce/client/Invoke-ShopdbEnforce.ps1: orchestrator. Fetches,
  optionally shadow-compares (installs from the share, only logs the diff), runs
  the unchanged engine, and reports. Fail-safe: any error exits 0.
- docs/GE-ENFORCE-CLIENT.md: the fetch + report contracts, config, cache/fail-
  safe behavior, the staged shadow -> read-cutover -> payload-migration runbook,
  and TLS/payload-integrity notes.

The report JSON shape matches the POST /api/geenforce/report contract already
covered by the reporting tests. Nothing here runs the live client; shadow mode
and cutover stay a site decision after Milestone 1 sign-off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 18:30:08 -04:00
cproudlock
adc5b7f69e Add GE-Enforce export-to-share + fleet-compliance UI (Milestone 1 UX)
All checks were successful
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 9s
Rounds out the Milestone 1 admin experience: author + publish in shopdb, push
to the share by a button, and see what the fleet actually did.

Export to share:
- GET/PUT /api/geenforce/config stores the on-share export root (Setting
  geenforce_share_root); POST /scopes/<id>/export-share writes the current
  published JSON to <shareroot>/<scope>/manifest.json (preinstall.json for the
  preinstall phase), backing up the existing file to _meta/history first.
  geenforce.publish gated. The engine and PCs are untouched - this is the safe
  Milestone 1 push whose rollback is restoring the history backup.
- Editor: a share-root config row + an "Export to Share" button per scope.
- 3 tests (config roundtrip, export writes the file, second export backs up).

Fleet-compliance UI (Settings > Enforcement Reports):
- New page over GET /reports + /reports/<id>: latest report per PC with
  received (applied vs latest published version), status (ok/selfhealed/failed),
  and install/skip/fail counts; row detail shows per-entry outcomes with
  self-heal flags, exit codes, and messages. Hostname/PC-type filters.
- ADR-010 settings card + ADR-009 plugin-gated route.

Full suite 883 green; frontend build + naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 18:06:33 -04:00
cproudlock
7eb6cebeb5 Add GE-Enforce P3 manifest editor UI (Settings > Imaging PC Types)
All checks were successful
CI / backend (push) Successful in 1m41s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
The imaging-PC-type manifest editor, contributed as an ADR-010 settings card
(Integrations group) and an ADR-009 plugin-gated route
(/settings/imagingpctypes, hidden when geenforce is disabled).

- Scope list: every imaging PC type with phase, entry count, and published
  version (or "unpublished"). New PC Type button.
- Scope detail: ComputerType/MeasuringToolType mapping + description; Publish,
  Versions (with per-version Roll Back), Preview (draft JSON), Delete.
- Entry table: ordered with Move Up/Down (the ordering contract, not drag),
  Name/Type/Detection/Filters, Edit/Delete. Add Entry opens a typed modal whose
  fields switch on entry Type (MSI/EXE/... vs PS1 vs File vs Registry), with a
  detection block, comma-separated targeting filters, CMM version gate, payload
  source, and an Advanced disclosure for the inert ApplyMode/UpdateWindow and
  InUseCheck. RegValue is typed by RegType (DWord/QWord -> number).
- Simulator: "what would a PC get" - enter a machine profile, see which entries
  apply and which filter excluded the rest. Verified live: CMM version 2019 ->
  applies 2019 + untagged, filters 2016/2026 by _CmmVersion.

Uses the P2 admin API; JWT+admin gated. Frontend build + naming green; full
backend suite 876 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 17:22:49 -04:00
cproudlock
d157502d5b Add GE-Enforce P2 admin CRUD API: scopes, entries, reorder, simulate, publish
Full HTTP admin surface behind the manifest editor (geenforce.manage for edits,
geenforce.publish for shipping):

- Scopes: POST/GET/PUT/DELETE /scopes[/<id>] (create imaging PC types, edit the
  ComputerType/MeasuringToolType mapping + metadata, delete).
- Entries: POST /scopes/<id>/entries, PUT/DELETE /entries/<id>. Payloads use the
  manifest Applications[] shape; populate_entry (refactored out of build_entry)
  updates an entry in place, resetting omitted fields and replacing children.
- Reorder: PUT /scopes/<id>/entries/reorder enforces the ordering contract
  (body must list exactly the scope's entry ids).
- Simulate: GET /scopes/<id>/simulate?pctype&subtype&hostname&machinenumber&
  cmmversion returns which entries apply and which filter excluded the rest,
  reusing the engine-mirror filters. The "what would this PC get" tool.
- Publish lifecycle: POST /scopes/<id>/publish (records publishedby from JWT),
  GET /scopes/<id>/versions, GET .../versions/<n> (frozen manifest),
  POST /scopes/<id>/rollback.

Entry type validated against ENTRY_TYPES; 8 CRUD tests. JWT+permission gated so
the authz sweep covers them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 17:13:27 -04:00
cproudlock
6dc31c6149 Add GE-Enforce observed-state reporting: receipt + self-heal from PCs
All checks were successful
CI / backend (push) Successful in 1m37s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
PCs now report enforcement results back to shopdb, closing the desired-vs-observed
loop.

- POST /api/geenforce/report (geenforce.report service token): each cycle a PC
  posts the published version it applied, install/skip/fail/filtered counts, and
  per-entry outcomes.
- Two tables: manifestenforcementreports (latest-per-host + history: applied
  version, enforcer version, counts, derived status ok/selfhealed/failed) and
  manifestenforcementresults (per entry: action installed/skipped/failed,
  selfhealed flag, exit code, warning/error message).
- RECEIVED: reports carry the applied version; the admin view derives
  receivedlatest by comparing it to the scope's current published version, so
  the fleet view shows which PCs picked up an update.
- SELF-HEAL: per-entry action captures drift correction (installed when it
  should already be present) vs skipped (already good) vs failed, with messages.
- Admin reads: GET /reports (fleet compliance rollup) and GET /reports/<id>
  (per-entry detail). New geenforce.report permission.
- Tables added to the (undeployed) 0001 baseline; geenforce.post_report is a
  service-token endpoint so it is exempt from the JWT authz sweep, like the
  collector blueprint. 8 reporting tests; full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 17:04:07 -04:00
cproudlock
d85b33bd68 Build GE-Enforce manifest-store plugin (P0/P1): model, importer, parity gate
All checks were successful
CI / backend (push) Successful in 1m36s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
First execution phases of docs/proposals/ge-enforce-plugin.md. The GE-Enforce
manifest becomes shopdb data.

P0 scaffold: new geenforce plugin (api_prefix /api/geenforce, default_enabled
false, core_version >=0.7.0). Registered in PLUGIN_TABLE_OWNERS (ADR-008); its
0001 baseline really creates the tables.

P1a model: one wide manifestentries table + entrytype discriminator (not STI,
not JSON blob), manifestscopes (UNIQUE scopename+phase), the three multi-value
filter child tables, inusechecks + processes, immutable manifestpublishedversions
(frozen rendered JSON), manifestpayloads (inline, capped), pctypealiases
(mirror of the engine lib's alias graph). regvalue stored as its raw JSON
literal so DWord typing survives.

P1c importer + exporter: parse common + gea-shopfloor-* + preinstall.json into
draft rows and rebuild the JSON verbatim from rows in sortorder.

P1d parity harness (GATE A): filters.py mirrors the engine's four filter
functions + alias graph; parity.py proves import+export is behaviorally lossless
(field-identical + same-entries-fire across 18 machine-profile fixtures) WITHOUT
byte-diffing. Verified PASS against all 11 real reference manifests (64 entries)
and a synthetic site-neutral fixture covering every type/filter (the CI gate).

First slice (gea-shopfloor-cmm shape): service layer (import/publish/rollback/
export-to-share), CLI (parity, import-share, publish, export-share), and the
client endpoint GET /api/geenforce/manifest serving the current published
snapshot (never the draft) with ETag/304. Split permissions
geenforce.manage/publish/fetch. Tests prove import->publish->serve, draft edits
never change served bytes, publish+rollback, and auth (401 unauth/wrong-scope).

Contract 0.11.0: added service_token_authorized(scope) to shopdb.api so plugin
service endpoints authorize a scoped managed token without importing core token
internals. Documented in PLUGIN-HOOKS.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 16:53:18 -04:00
cproudlock
cf2f9c308e Add manifest revision history + draft audit trail to GE-Enforce plan
All checks were successful
CI / backend (push) Successful in 1m32s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
- Every publish is a permanent immutable revision (manifestpublishedversions),
  kept indefinitely; optional retention policy (keep last M / prune older than N)
  deferred, default keep-everything.
- Draft edits are not versioned (working copy overwrites), so field-level "who
  changed what between publishes" rides the existing core audit system - no new
  table, shows in the Audit Logs UI IT already uses.
- Runbook: History tab for published versions + roll back; Audit Logs for draft
  edit provenance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 16:24:28 -04:00
cproudlock
c88d673d7b Fold Fable execution review into GE-Enforce plan: simpler, IT-manageable
All checks were successful
CI / backend (push) Successful in 1m25s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Add an execution plan and simplify the design for average-site-IT operability
(the governing constraint from the review):

Model simplifications:
- One wide manifestentries table with an entrytype discriminator, not SQLAlchemy
  STI subclasses and not a JSON blob. ~64 total entries fleet-wide make sparse
  columns free and keep rows readable in plain SQL.
- Published snapshots freeze the rendered JSON document in a single manifestjson
  column; drop the row-mirrored manifestpublishedentries family. Immutability is
  structural, rollback is a one-flag flip, diff is a text diff.
- New manifestpayloads table for inline bytes with a ~1 MB app cap.
- regvalue stores the raw JSON literal (DWord typing); applymode/updatewindow
  flagged inert-in-engine so the UI labels them.

Execution plan (section 13):
- Phases P0-P6 with gates; parity harness spec (two checks, IT-readable output,
  ~16-18 machine-profile fixtures); first vertical slice through
  gea-shopfloor-cmm; ranked fail-fast risks.
- Milestone 1 = author + publish in shopdb, export to the share by a button,
  engine/dispatcher/PCs unchanged. Real pain relief at zero client risk, with a
  rollback IT already knows (restore the _meta/history backup).
- Export-to-share promoted to a first-class feature and permanent break-glass.
- Split permission geenforce.manage (edit) vs geenforce.publish (ship).
- Move Up/Down instead of drag-and-drop; a "what would this PC get" simulator
  endpoint + UI; three-increment editor build.
- Two-source pctypemap transition window; scope-inventory reconciliation
  (gea-shopfloor-display has no share dir).
- Plain-English IT day-to-day runbook proving the design is manageable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 16:15:41 -04:00
cproudlock
9dd2aa3cc6 Revise GE-Enforce plugin plan after review: parity, integrity, snapshots
All checks were successful
CI / backend (push) Successful in 1m25s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
Fold six review findings into docs/proposals/ge-enforce-plugin.md:

- Parity gate is behavioral equivalence, not byte-identity. Re-serialized JSON
  differs in key order/whitespace/_comment formatting, so a raw diff never
  converges; the test is same ordered entry set with identical detection/
  targeting/action per entry.
- Dedicated payloadsha256 column, independent of detectionmethod. DetectionValue
  is a SHA256 only for detectionmethod=Hash; MSIs with Registry/FileVersion
  detection carry no payload hash, so an HTTP/inline fetch would otherwise run
  unverified bytes. Client verifies fetched bytes against payloadsha256.
- Immutable published snapshots (manifestpublishedversions). Editing touches a
  draft only; publish freezes a snapshot; the client is always served the latest
  published snapshot, never the live draft; rollback republishes a prior
  snapshot (the post-cutover safety net once the on-share JSON is retired).
- Scope uniqueness is (scopename, phase), not scopename alone; preinstall is one
  flat scope gated internally by PCTypes, not per-pctype scopes.
- Alias graph: engine lib stays the single source of truth, shopdb only mirrors
  it for validation; do not invert to engine-fetches-from-shopdb.
- Desired-vs-observed needs a new collector field (the installedVersions status
  map), not existing data; flagged as a dependency.

Plus TLS trust for the SYSTEM-context client and importer skips .bak variants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 15:34:40 -04:00
cproudlock
1672e349e5 Collector auto-links measuring tools for metrology PCs; settings rail cleanup
All checks were successful
CI / backend (push) Successful in 1m25s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Metrology PCs (CMM, Keyence, Genspect, wax-and-trace imaging pc-types) drive
an attached measuring instrument. The PC itself stays a shopfloor PC, but the
collector now models the instrument:

- New METROLOGY_TOOL_MAP (pctypemap.py) maps those pc-types to a
  MeasuringToolType (CMM, Vision System, Genspect, Form Tracer).
- ComputersPlugin._sync_measuringtool_link creates the MeasuringTool asset
  once and a directional PC->tool "controls" relationship, tagged
  collector:measuringtool. Idempotent (re-push reuses, no duplicate asset) and
  self-archiving (a PC re-imaged to a non-metrology type deactivates the link
  but keeps the asset and any calibration history). Mirrors the printer-link
  pattern. The MeasuringToolType is created on demand if not seeded.
- 4 tests: create+link, idempotent re-push, non-metrology skip, repurpose
  archives. Non-metrology PCs never warn about a missing controls type.

Settings rail cleanup:
- Collapsible groups so the 13-group rail fits without scrolling (1511px ->
  488px). The group containing the current page expands; the rest collapse.
  CSS-drawn caret (ASCII source, no Unicode). Empty groups never render, in
  both the rail and the landing page.
- Measuring Tools group placed with the other asset groups (right after
  Machines) instead of appended last; empty placeholder positions the
  plugin-contributed cards.
- Operating Systems moved from PCs to General Reference: OS is cross-asset
  (PCs, machines, measuring tools, network devices all run one).

Plus docs/proposals/ge-enforce-plugin.md: a planning doc for refactoring
GE-Enforce/DSC into a shopdb plugin (manifest as shopdb data, payloads on
SMB/HTTP/inline), grounded in the real manifest schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 15:29:08 -04:00
cproudlock
f6dcaef4c0 Persist list pagination and search in the URL
All checks were successful
CI / backend (push) Successful in 1m25s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
List pages kept the current page in local state, so clicking into an
asset and hitting Back remounted the list at page 1. A shared
useListQuery composable now mirrors the page (and search term) into the
URL query via router.replace across all 18 list pages, so Back restores
the page you were on and lists are deep-linkable. Page 1 with no search
stays a bare path; changing a filter resets to page 1; unrelated query
keys are preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:19:50 -04:00
cproudlock
b2e1827e5d Normalize the asset detail card layout across all five pages
All checks were successful
CI / backend (push) Successful in 1m25s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
One canonical card order applied to machines, PCs, printers, network,
and measuring tools: Identity -> type-specific -> status -> Location &
Organization -> domain -> Custom Fields -> Warranty -> Relationships ->
Notes -> audit footer, with a documenting comment on each page so they
stop drifting. The location card is Location & Organization everywhere;
the network detail page is rebuilt into the family (Asset Information
folded into Identity, Record Info retired for the standard audit
footer, a Location & Organization card added). Fixed two latent bugs
found in the process: printer detail had no audit footer, and the
network Record Info read datecreated/datemodified (not in the payload)
so its timestamps rendered blank - the footer now uses the correct
fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:13:15 -04:00
cproudlock
275224822e Add collector PC->printer links and searchable custom fields
All checks were successful
CI / backend (push) Successful in 1m24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Collector: the computers collector schema gains defaultprinter and
printers; apply_collector_payload resolves each reported identifier to
a printer asset (windowsname/hostname/sharename/assetnumber/IP,
first-hit case-insensitive) and idempotently syncs relationships -
defaultprinter (directional) for the default, connectedto for the
rest. Collector-created rows are tagged so a re-report archives dropped
links while manual relationships are never touched; unresolved
identifiers warn instead of failing. Both PC and printer detail pages
show the links via the shared relationships card (no frontend change).
GE-Enforce Win32_Printer collection snippet documented.

Searchable custom fields: a per-field searchable flag (migration 7d24);
global search matches custom-field values on flagged active fields and
routes each hit to the asset detail page, reusing the existing
(type,id) dedupe and search_<type>_enabled domain filter. Searchable
toggle on the Custom Fields settings page.

822 tests pass; both verified live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:01:20 -04:00
cproudlock
9daf1578a7 Balance the two-column card layout on detail pages
All checks were successful
CI / backend (push) Successful in 1m24s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 7s
The content grid hand-assigned cards to a fixed left/right split, so a
PC or machine with many tall cards (installed apps, warranty,
relationships) piled them all on one side. Switched .content-grid to a
balanced CSS multicolumn flow (display:contents flattens the wrappers
so no markup changes), with break-inside:avoid keeping cards intact.
Cards now distribute by height and the columns stay even on every asset
detail page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:41:18 -04:00
cproudlock
d187a2c535 Fix PC installed-applications rendering; minor UI cleanups
All checks were successful
CI / backend (push) Successful in 1m24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
The PC detail Installed Applications section 500d and vanished on any
real PC: ComputerInstalledApp had no to_dict, so the endpoint errored
and the v-if hid the section. Added the serializer (curated version
wins over the raw collected string, app name + description included)
and aligned PCDetail to the flat payload; regression test added.

Also: employee detail skips its USB panels when the usb plugin is
disabled (was firing 404s), and the shopfloor kiosk header is now
light-on-dark for readability.

810 tests pass; PC 259 installed apps verified live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:37:58 -04:00
cproudlock
68b6949f1c Print a single label at a chosen ULINE sheet cell
All checks were successful
CI / backend (push) Successful in 1m24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
The single-label page gains an Output toggle (standalone vs place on a
ULINE 6-up sheet) with a 2x3 cell picker, so one label can be printed
into the correct physical position on a partially-used sheet - the
single-label equivalent of the batch page start-cell offset. Batch page
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:09:55 -04:00
cproudlock
cced6fe96e Release 0.7.0
All checks were successful
CI / backend (push) Successful in 1m23s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Cuts the large post-0.6.0 pile as a pinnable release: the machines
rename (ADR-011), the API import surface, personal/scoped/collector
API tokens and the get_permissions plugin hook, the ADR-010 frontend
hook contract, per-plugin migrations, model/employee photos, the
dualpath single-machine toggle and relationship propagation, support
teams, email sending, and the shared asset label generator. Plugin
contract moved 0.6.0 -> 0.10.0 over this range (distinct series per
ADR-007).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v0.7.0
2026-07-12 13:00:58 -04:00
cproudlock
55b42fed00 Fix sidebar footer overflow from the change-password link
All checks were successful
CI / backend (push) Successful in 1m23s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Adding the Change Password action turned the user menu into a
full-width-button row that overflowed the fixed sidebar. The footer is
now an identity row (avatar + truncating name) above a compact
icon-button actions row (Password / Logout), both within bounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:28:08 -04:00
cproudlock
4b20c2eb56 Add batch ULINE label printing for all asset types
All checks were successful
CI / backend (push) Successful in 1m24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
A multi-select batch sheet at /print/asset-label-batch/<type> lays
selected assets onto ULINE label pages (6-up 3x3 or dense 72-up mini),
with code-type and encode toggles matching the single label, plus a
start-cell offset for partial sheets. Print Labels buttons on all five
asset list pages. The per-type config and encode resolution are
extracted to a shared print/assetLabel.js used by both the single and
batch views. Measuring-tool batches encode each tool inspection-
operation code (decode-verified 0615).

808 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:08:22 -04:00
cproudlock
a846587f39 Add email sending (service + 3 flows) and a general asset label generator
All checks were successful
CI / backend (push) Successful in 1m23s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Email: a stdlib SMTP mailer (settings-first config, graceful no-op when
unconfigured), a test-email endpoint wired to the Email settings page,
forced first-login password change (users.mustchangepassword, migration
7d23, /change-password flow), new-user welcome mail, and on-demand
report/alert delivery (POST /api/reports/email + Email Report buttons)
with an external-cron-with-a-scoped-PAT path documented for automation.
All tests patch smtplib - no network.

Labels: a shared /print/asset-label/<type>/<id> view any asset detail
page opens - card or plain style, QR or barcode, configurable encoding.
Per-type qr_target_* templates plus label_default_style/codetype/encodes
settings on the Printing page. Measuring-tool labels default to encoding
their inspection-operation code (derived from the location name, e.g.
0615), so every tool in an area shares the area code - verified by
decoding the rendered QR. Machine labels default to the machine number;
blank-serial handled gracefully.

808 tests pass; both features verified live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:58:30 -04:00
cproudlock
7d309aabeb Final-pass polish: support contact UX, audit tooltip, doc refresh
All checks were successful
CI / backend (push) Successful in 1m21s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Support teams: contact management moved from a row expander to a modal
(Contacts (N) button per team); application detail Support card and the
modal show Email (mailto) and Teams chat buttons for contacts with an
SSO, derived as sso@ + a new contact_email_domain site setting
(default geaerospace.com, blank hides the buttons).

Audit log: hovering a user SSO shows the full name, resolved
best-effort from the employee directory in either mode.

Docs/hygiene from a standards review: CLAUDE.md active-state,
CONTRACT-STABILITY.md and README brought to contract 0.10.0 / 11
plugins / migration head 7d22; get_asset_panels endpoint path fixed in
the hook docstring; leftover debug console.logs removed.

781 tests pass; contacts modal, action-button hrefs, and the audit
tooltip verified live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:22:01 -04:00