# Roadmap **Hand-written, not generated, and it drifts.** Current contract and product versions, the plugin inventory, every Alembic chain head and the codebase size are in [PROJECT-MAP](PROJECT-MAP.md), which IS generated; nothing of that kind belongs here. This page is a different thing: what the product can do today, and the short list of what is left. It supersedes nothing in the ADRs. shopdb-flask is pre-1.0 on both series. ## What has shipped Read this as the answer to "does it already do X". Each row names the page that covers it, so this stays a map rather than a second copy of the docs. ### The platform | Capability | Where it is documented | |---|---| | One instance per site, single-tenant, with its own database, users, secrets and enabled plugins | [ADR-004](adr/ADR-004-deployment-topology.md) | | `Asset` as the one model a plugin may build on, with relationships, propagation and map position on it | [ASSET-COMPOSITION](ASSET-COMPOSITION.md), [ADR-001](adr/ADR-001-asset-as-platform-contract.md) | | A versioned plugin contract, with `core_version` ranges enforced at load | [CONTRACT-STABILITY](CONTRACT-STABILITY.md), [ADR-002](adr/ADR-002-plugin-versioning.md) | | Manifest-first plugin loader, `shopdb.api` as the only import surface, auto-registered blueprints | [PLUGIN-HOOKS](PLUGIN-HOOKS.md) | | Frontend hooks - settings cards, asset panels, map overlays, asset presentation - so a plugin contributes UI without editing core Vue | [ADR-010](adr/ADR-010-frontend-plugin-hooks.md) | | Backend-driven route gating, so a disabled plugin's URL redirects instead of breaking | [ADR-009](adr/ADR-009-frontend-plugin-gating.md) | | Per-plugin Alembic chains, owned by the plugin, applied by `flask plugin upgrade-all` | [ADR-008](adr/ADR-008-plugin-migration-ownership.md) | | Lean per-site builds: a site carries only the plugins it chose, and `plugin prune-schema` drops the rest of the tables | [ADR-013](adr/ADR-013-plugin-catalog-and-lean-builds.md), [ADR-014](adr/ADR-014-schema-lean-per-site.md) | | Plugin signing and a verification gate a site can switch on | [PLUGIN-SIGNING](PLUGIN-SIGNING.md) | | Plugins shipped from their own repositories, dropped into `plugins/` | [PLUGIN-EXTERNAL-REPO](PLUGIN-EXTERNAL-REPO.md), [ADR-003](adr/ADR-003-plugin-distribution.md) | | `flask plugin new` scaffolding, with an external-plugin harness to prove one loads | [PLUGIN-QUICKSTART](PLUGIN-QUICKSTART.md), [PLUGIN-EXTERNAL-REPO](PLUGIN-EXTERNAL-REPO.md) | | RBAC with declared-and-enforced permissions, account lockout and login rate limiting | [CONFIG](CONFIG.md) | | Every read behind a permission, and `guest` as a seeded role rather than a switch per feature - a site opens a door in the same role grid it uses for everybody else, and what a guest may be granted is bounded | [CONFIG](CONFIG.md), [PLUGINS](PLUGINS.md) | | An append-only audit log with a retention period, a size-and-impact preview, and a purge that runs only when asked | [CONFIG](CONFIG.md) | | Generated OpenAPI served at `/api/docs`, with `llms.txt` for machine callers | [llms.txt](llms.txt) | | A dependency inventory (SBOM) and advisory scanning shipped with every release | [DEPENDENCY-SECURITY](DEPENDENCY-SECURITY.md) | ### The domain | Capability | Where it is documented | |---|---| | PCs, printers, machines, network devices, applications, warranties | [PLUGINS](PLUGINS.md) | | Metrology instruments filed as machines, with the measuringtools split retired | [ADR-018](adr/ADR-018-metrology-instruments-are-machines.md) | | The equipment domain renamed to machines, and the models catalog retyped by modeltype | [ADR-011](adr/ADR-011-machines-rename.md) | | Staff names resolved from the site's own HR database rather than copied into ShopDB - global search and audit-log attribution | [CONFIG](CONFIG.md) | | Several devices under one asset number, as composition rather than duplicate records | [ASSET-COMPOSITION](ASSET-COMPOSITION.md) | | Floor map as buildings and levels, with landmark-pair transforms to move markers onto a redrawn plan | [FLOOR-MAP](FLOOR-MAP.md), [ADR-017](adr/ADR-017-buildings-and-levels.md) | | Procedures as typed, checkbox-able blocks inside ShopDB instead of documents on a share | [GUIDES](GUIDES.md) | | A knowledge base, notifications and boards, slides and display kiosks | [PLUGINS](PLUGINS.md), [EVENTSAVER](EVENTSAVER.md) | | Secure Send - a one-view expiring link for handing somebody a password | [SECURE-SEND](SECURE-SEND.md) | | Technician utilities that work on an air-gapped bench: barcode and QR label generation, two-sided labels, unit conversions | [PLUGINS](PLUGINS.md) | | Search across the inventory from one box, with each domain answering only to a caller holding that domain's permission | [CONFIG](CONFIG.md) | | Retiring a record from the interface as a soft delete, so a scrapped tool leaves the lists and keeps its history | [ASSET-COMPOSITION](ASSET-COMPOSITION.md) | | A vendor and model catalog with hygiene commands - dedupe, merge and rename - for the twins legacy data carries in | [CSV-IMPORT](CSV-IMPORT.md) | | A worst-first dashboard queue with filters | [proposals/dashboard-live-fleet](proposals/dashboard-live-fleet.md) | | Backups as a first-class feature, with kinds, a staleness card and a restore path | [BACKUP-RESTORE](BACKUP-RESTORE.md), [BACKUP-STALENESS](BACKUP-STALENESS.md) | ### The fleet | Capability | Where it is documented | |---|---| | A generic collector contract: a plugin declares a schema and gets an ingest endpoint with auth | [COLLECTOR-PAYLOADS](COLLECTOR-PAYLOADS.md), [ADR-006](adr/ADR-006-collector-contract.md) | | Shipped PowerShell reporters for PC inventory and printer queues | [COLLECTOR-CLIENT-SETUP](COLLECTOR-CLIENT-SETUP.md) | | A C# endpoint agent with a command channel, a tray app and staged upgrades | [FLEET-ARCHITECTURE](FLEET-ARCHITECTURE.md) | | **Per-PC credentials**: each endpoint enrolls, holds its own secret, and can be revoked alone | [COLLECTOR-AUTH](COLLECTOR-AUTH.md), [ADR-019](adr/ADR-019-per-pc-endpoint-credentials.md) | | Server public-key pinning on the agent, with the pin set authoritative from the registry | [COLLECTOR-AUTH](COLLECTOR-AUTH.md) | | Printer installation driven from ShopDB, with printers as a property of the machine | [PRINTER-INSTALLER](PRINTER-INSTALLER.md) | | Display kiosks and the event saver | [EVENTSAVER](EVENTSAVER.md), [FLEET-ARCHITECTURE](FLEET-ARCHITECTURE.md) | | Adopting the shop-floor tools at a site that is not this one | [ADOPTING-AT-ANOTHER-SITE](ADOPTING-AT-ANOTHER-SITE.md) | ### Getting a site running and filled | Capability | Where it is documented | |---|---| | Docker deployment: build, migrate, seed and serve behind the stack's own nginx | [DEPLOY](DEPLOY.md) | | Air-gapped deployment from an image tarball, nothing pulled | [DEPLOY-AIRGAP](DEPLOY-AIRGAP.md) | | Pre-built frontend builds, for a box where npm cannot reach the registry | [DOCKER-PREBUILT-APPROACH](deployment/DOCKER-PREBUILT-APPROACH.md) | | Upgrading a running site, with the schema moved forward before the app serves | [UPGRADE](UPGRADE.md) | | A first-run setup wizard that creates the first admin and captures site identity | [DEPLOY](DEPLOY.md), step 6 | | Settings-driven branding, ServiceNow integration and site identity, so nothing is hardcoded per site | [ADR-015](adr/ADR-015-site-specific-configuration.md) | | CSV import for sites with no developer, and a scripted import API for sites with a source database | [CSV-IMPORT](CSV-IMPORT.md), [IMPORT-API](IMPORT-API.md) | | The completed classic-ASP migration, as a worked mapping | [IMPORT-LEGACY-ASP](IMPORT-LEGACY-ASP.md) | | Guides that travel: a procedure exports with its images in one file and imports at another site, and an existing HTML article converts into one | [GUIDES](GUIDES.md) | ## What is left Two items. Everything else that was on this list is either done and listed above, or deferred below. ### 1. Finalize a distributed release The product runs at the site that built it. Making it something another site receives, rather than something that is stood up by the people who wrote it, is the remaining work. **Docker is the shape a receiving site gets,** and on this line it is the only one: the Windows/IIS installer topology is not carried here. What is missing: - **Published images, pulled rather than built.** `docker-compose.pull.yml` and `docker-compose.airgap.yml` already run a stack from an image instead of a source build, and the image-publishing workflow exists with its channels decided: a release tag publishes `:stable`, the trunk publishes `:edge`, and both carry a short-SHA tag for rollback. It is not switched on. Its push triggers are commented out and it runs only by hand, because nobody has confirmed the registry package exists under this owner or decided who may pull it. Turning that on, or producing an offline bundle for a site that cannot reach a registry, with the checksum and the upgrade path written as one procedure rather than three, is the work. [DOCKER-PREBUILT-APPROACH](deployment/DOCKER-PREBUILT-APPROACH.md) ends with the shape of it. - **One command to stand a site up.** Today a first deploy is a `.env` to fill, a certificate to place, a plugin profile to apply and an admin to create. Each step is documented and none is hard; together they are the reason an adopting site needs us on the call. - **A receiving site's upgrade story, end to end.** [UPGRADE](UPGRADE.md) covers a site that has the source. A site that was handed an image needs the same page written for the artifact it actually holds. - **Prove it by adopting at a site that is not this one.** [ADOPTING-AT-ANOTHER-SITE](ADOPTING-AT-ANOTHER-SITE.md) is written and has not been walked by somebody with no access to the authors. The 2026-09-07 [production readiness assessment](deployment/PRODUCTION-READINESS.md) is the record of why this is the item, and several of the gaps it names have closed since. ### 2. Setup wizard polish The wizard exists and is the primary path to a first admin: `/setup` runs while `setup_complete` is false, creates the first administrator behind a setup token or a loopback request, seeds starter vendors, and captures site identity. Polishing it is what turns a working wizard into the thing an adopting site is handed: - Carry the steps that are still command-line - choosing and enabling plugins, the floor plan, the collector key - into the wizard, so a site that never opens a terminal gets a complete instance. - Say what each choice costs and what can be changed later. A first-run screen that quietly makes an irreversible decision is worse than a prompt. - Make it re-runnable for the parts that are safe to re-run, so a site that skipped a step is not stuck with the CLI as the only way back. - Finish it against a genuinely empty instance, repeatedly, and on the air-gap path where there is no network to lean on. ## Deferred, deliberately Out of scope for 1.0. Each has a reason, and the reason is the point. - **Multi-tenancy.** Rejected by [ADR-004](adr/ADR-004-deployment-topology.md). Revisit only if more than five sites adopt and the per-site overhead becomes painful. - **Pip-installable plugins.** Deferred per [ADR-003](adr/ADR-003-plugin-distribution.md). Filesystem distribution stays the v1 model. - **An event bus on `BasePlugin`.** Removed per [ADR-001](adr/ADR-001-asset-as-platform-contract.md); add it via a new ADR if a real use case appears. - **A frontend rebuild** beyond Vue 3 + Pinia + Vite. The stack is fine. - **Full palette theming.** Brand colors are settings-driven; the rest of the palette is still in `style.css`. Worth doing, not worth blocking on. - **A marketplace listing site.** [PLUGINS](PLUGINS.md) is a one-pager, and that is enough until there are more than three external plugins. - **Contract surface diff tooling.** Version bumps are manual judgement today. A CI check that diffs the surface against the previous tag would catch a missed bump - see [ADR-002](adr/ADR-002-plugin-versioning.md). - **File sharing by expiring link.** The secret half shipped as [SECURE-SEND](SECURE-SEND.md); the file half was dropped on purpose, and dropping it removed the export-control question entirely, along with blob storage and the need to authenticate a recipient. What would have to be settled before it comes back, so nobody rediscovers it: a FILE recipient should be logged in too, which costs nothing on an intranet-only server where everyone with the link already has an account; sharing a file wants its own permission separate from sharing a secret, because the risk classes differ; and zero-knowledge encryption is right for a credential but questionable for a controlled document, where "the server cannot say what was in it" is a liability rather than a feature. Export control should confirm internal link sharing in writing before anything here carries drawings or NC programs. - **Calibration cycles, maintenance windows, downtime tracking.** Domain extensions that would live in the `machines` plugin, not framework work. ## What 1.0.0 means Tagging `1.0.0` is a commitment that the contract surface is stable for at least the next minor version cycle: a plugin author at another site can pin `core_version: ">=1.0.0,<2.0.0"` and trust the plugin will survive framework patches. Concretely, it ships when: 1. A site that did not build the product can receive a release, install it and upgrade it without us - the distributed release item above. 2. The setup wizard takes a site from empty to usable without a terminal. 3. The contract test suite covers every documented hook with both a happy path and a broken-plugin failure-isolation test. 4. At least one external plugin has been built end to end, from its own repository, with no contract change needed mid-build. ## Decision log pointers When something here gets prioritized, the why goes in a new ADR and this file links it. The ADRs are canonical for design decisions and [adr/README](adr/README.md) is their index, including which ones still hold. This file is only the prioritized backlog.