Roadmap
What is planned, and what has been ruled out
Edit this pageOn this page
- How to read this
- 0.2 — Close the credibility gaps
- 0.2.1 — A deployment you can actually start
- 0.2.2 — Work that happens without a browser open
- 0.3 — Integrations and alerting
- 0.4 — Programmable Loomscope
- 0.5 — Operable at scale
- 1.0 — Enterprise readiness
- Considering
- Declined
- Known gaps
- Influencing this roadmap
This is what Loomscope intends to build, in the order it intends to build it, and — just as importantly — what it has decided not to build.
Dates are deliberately absent. Loomscope is maintained by one person; sequence is a commitment, timing is not.
Current release: 0.13.1. Usable, covered by CI, not yet stable.
Every release below carries an entry in CHANGELOG.md with the same four headings, in this order, and a heading is omitted only when it is genuinely empty:
- Security — anything that changed a boundary. First, always, because it is the section that decides whether an upgrade is optional.
- Added — new capability, named from the operator's side.
- Fixed — with the symptom stated, not just the cause. "The linter had never loaded its configuration" is more useful to a reader than "fix lint config".
- Known defects — what shipped broken, and the workaround. A release that hides one of these buys a week and spends a customer's trust.
How to read this
Each item carries a state:
| State | Meaning |
|---|---|
| Shipped | In main and covered by CI. |
| Next | Committed to, and the next thing to be worked on. |
| Planned | Agreed in principle, with an ADR where the design is non-obvious. |
| Considering | Under analysis. May be declined. |
| Declined | Decided against. The reasoning is recorded so it is not relitigated. |
0.2 — Close the credibility gaps
The theme is removing the distance between what Loomscope claims and what it does. Nothing here is a new capability; all of it is making the existing surface trustworthy.
| Item | State | Notes |
|---|---|---|
| Shipped | A root flat config now runs in CI. See ADR-0008. | |
| Test coverage on the HTTP API | Next | Twenty-one of twenty-eight. The whole daemon protocol — register, heartbeat, jobs/poll, observations, credentials — plus /api/v1/metrics and the six public collections, whose suite runs every invariant against every collection so that one endpoint quietly disagreeing with the others fails the build. That is the entire surface a third party integrates against. The session-authenticated routes are now covered too, by one suite asserting the property that matters uniformly across them — a caller the route cannot identify is refused, and refused with a response rather than a thrown error. It found two routes resolving the session with no guard, so an expired cookie produced a 500 with a stack trace where a 401 belonged. The remainder are third-party adapters (Better-Auth, tRPC) and the SCIM surface, which scripts/scim-e2e.sh covers. |
| Shipped | It did not hold. The application connected as a superuser, which PostgreSQL exempts from row-level security entirely. Scoped transactions now drop to an unprivileged role, and eight tests assert the boundary against a real database. | |
| Shipped | next build and every pure-logic test now run with no environment at all. | |
| Shipped | It no longer prints a single-use credential to stdout; it refuses instead, until a mail transport exists. | |
| Shipped | Every colour literal is gone; one token set drives both themes. See ADR-0007. | |
| Shipped | Blueprint: warm neutrals, a copper accent that frees blue to mean information, square controls, hairlines instead of shadows, and data set in mono. One token file moves the application and the documentation site together. See ADR-0022. | |
| Shipped | Settings → Daemons: enrol, rotate a key, reassign a site, retire. The key is shown once and only its hash is stored. Verified end to end — a key issued by the screen registers a daemon and passes a heartbeat, and is rejected against any other daemon id. | |
| Shipped | The share panel lists every link for a topology — when it was made, whether it carries a password, when it expires — and revokes one. Verified: a live link serves 200, and 404s the moment it is revoked. | |
| Operator surface — actions, settings, topology | Next | ADR-0009. The dead ends are closed, and the topology visualiser of item 4 now produces drawings that fit a screen rather than 21:1 ribbons. Item 6 gained the piece that was blocking everything else: Settings → API keys, which the REST API, the CLI and the MCP server all told people to use and which did not exist — every key used to build them was inserted with SQL. Member management shipped earlier and this row claimed otherwise. What remains of item 6 is organisation rename and retention; item 5 has its first piece: host removal and restore, with a page to restore from — without one, "Remove" is indistinguishable from "Destroy" to whoever presses it. Tagging, credential rotation and topology rename remain. |
| Browser tests on the critical paths | Partial | Playwright is installed and nine tests are written against the four paths the README named — sign-in and its redirect, the host list with search and row navigation, a topology that actually paints nodes, the vulnerability filters. They have never been run. Chromium needs system libraries that playwright install --with-deps installs with sudo, which was not available where this was written, so the suite is unverified rather than passing. One command closes it: pnpm -C apps/server exec playwright install --with-deps chromium, then make test-e2e-browser. |
| Shipped | /api/sse read the tenant from a query parameter and middleware exempts the route from its cookie check, so nothing verified membership. It resolves the org from the session now. | |
| Shipped | Every list page ended in a hardcoded LIMIT with no way past it. Keyset cursors throughout, verified against real row counts for duplicates and gaps. | |
| Shipped | Two pairings failed. A test over the token file keeps them passing. | |
| Shipped | loomscope.dev — the Markdown in docs/ rendered on the product's own tokens. It had not built at all for weeks; CI builds it now. | |
| Shipped | Decided and done in 0.3.2: removed. This row contradicted the one twelve lines below it, which had recorded the same work as shipped. All that remains is a capabilities.ebpf boolean in the daemon contract, which every daemon reports as false — harmless, and cheaper to leave than to break the contract over. |
0.2.1 — A deployment you can actually start
The 0.2 audit found that the product has no front door. Nothing in the shipped code ever creates an organisation or a membership — the only such statements live in tests and the seed script — and there is no sign-up page, no invitation and no role change. The quick start says "sign in"; a new installer has nothing to sign in with.
| Item | State | ADR |
|---|---|---|
| First-run setup, invitations, role changes | Shipped 0.3.0 | ADR-0011 |
| CVE feed on by default, with a staleness line | Shipped 0.3.1 | ADR-0015 |
| One Postgres connection budget per process | Shipped 0.3.1 | ADR-0019 |
| The job runner no longer deadlocks itself | Shipped 0.4.1 | ADR-0021 |
| OIDC single sign-on with JIT provisioning | Shipped 0.5.0 | ADR-0016 |
| Slack and Teams alerting adapters | Shipped 0.6.0 | ADR-0013 |
| Failed scheduled ticks are retried | Shipped 0.6.1 | ADR-0012 |
| TLS chain validation | Shipped 0.7.0 | ADR-0014 |
| PagerDuty and Jira alerting adapters | Shipped 0.8.0 | ADR-0013 |
| Shipped | Users, Groups, group-to-role, and deprovisioning that ends the sessions somebody already holds. | |
| SCIM Groups and group-to-role mapping | Shipped 0.10.0 | ADR-0016 |
| Public REST API, CLI and MCP server | Shipped 0.11.0 | ADR-0003 |
| The daemon on Windows and macOS | Shipped 0.11.0 | ADR-0025 |
| nmap detected and reported (not yet used) | Shipped 0.11.0 | ADR-0024 |
/hosts responds to in-page navigation | Shipped 0.5.1 | Was a <form action="/hosts"> string action |
Both features here are small. They are grouped as a patch release because each removes a way a first-time installation silently does nothing useful.
0.2.2 — Work that happens without a browser open
| Item | State | ADR |
|---|---|---|
| Shipped | Superseded. A dedicated worker container with PostgreSQL advisory locks covers the need; River stays a maybe. See ADR-0012. | |
| Shipped | The compose stack ships a worker service, and LOOMSCOPE_JOBS_IN_WEB defaults to on so a deployment without one still runs its scans. | |
| Shipped | Partial. A failed tick retries within a run, with backoff derived from the job's interval. Not across a restart — that gap is listed under Known gaps. |
This is the release after which the README stops telling operators to treat the server as a single instance.
0.3 — Integrations and alerting
Loomscope currently tells you things only if you go and look. This is the release where it can tell you without being asked.
| Item | State | Notes |
|---|---|---|
| Shipped | The generic case, and the one that unblocks everything else via n8n or Zapier. | |
| Shipped | Incoming webhooks, where the URL is itself the credential. | |
| Shipped | Partial. Jira ships; Linear does not. | |
| Shipped | Events API v2, with the endpoint supplied rather than typed. | |
| Shipped | Event type plus condition already shipped with the pipeline; what was missing was the dry run, and it is the half that stops a rule from paging somebody four hundred times over a weekend. Each rule now carries what it would have done over the last 30 days, reconstructed from the tables the emitters read — not from notification_deliveries, which only holds events that already matched a rule and would answer a different question while looking like an answer. service.changed and daemon.offline say they cannot be previewed rather than returning a confident zero: neither keeps the history a replay needs. | |
| Shipped | Exponential backoff, five attempts, per-event-key deduplication so a flapping daemon cannot spam a channel. |
Event types: host.added, host.removed, service.changed, tls.expiring,
vuln.critical, vuln.high, snapshot.diff_significant, daemon.offline.
The pipeline is the decision, not the destination list — see ADR-0013. Two items ride on it:
| Item | State | ADR |
|---|---|---|
| Shipped | ADR-0014 | |
| Remove the eBPF scaffold | Shipped 0.3.2 | ADR-0018 |
0.4 — Programmable Loomscope
Three interfaces onto the same inventory, for three different consumers. Each has its own ADR because each could reasonably have been declined.
| Item | State | ADR |
|---|---|---|
| Shipped | ADR-0003 accepted 2026-08-18. The read surface ships: eight collections — hosts, services, networks, sites, certificates, vulnerabilities, snapshots, topologies — behind one reader, so they cannot each grow their own opinion about limits, cursors and unknown parameters. lsk_ keys with scopes floored by the membership's current role, RFC 9457 problem details with a request id, keyset pagination that refuses an out-of-range limit rather than clamping it. A test compares each endpoint's accepted parameters against the published document in both directions, so the contract cannot silently drift from the server. The four write operations ship too — register a network, trigger a scan, change a finding's state, take a snapshot — each leaving an audit row attributed to the key that acted. scan:run is a separate scope from inventory:write, because sending packets across a customer's network is a different power from changing a record. | |
loomscope command-line client | Shipped | ADR-0004. A single static Go binary, generated against the same OpenAPI document as the daemon's client so the two cannot come to describe different APIs. Human tables by default, --output json or csv for a pipe, and exit codes that carry meaning — 3 for --fail-on-match specifically, so a build step can tell "I found the CVE you asked me to watch for" from "the server was unreachable"; collapsing those into 1 is how a broken check comes to look like a passing one. There is no --api-key flag: argv is readable in ps and lands in shell history, so typing one earns an error telling you to rotate the key rather than a silent success. See the CLI reference. |
| Shipped | ADR-0005. loomscope mcp serves the inventory over stdio to any MCP-capable client — read-only, and scoped to the key it starts with, because every call goes through the same public API with the same row-level security. It never calls a model: the in-app assistant calls a model from Loomscope, this exposes Loomscope to somebody else's. The Streamable HTTP transport the ADR also specifies ships too, for clients that cannot spawn a process — authenticated with a bearer token it refuses to start without, bound to loopback by default, and validating every request's Origin because a browser will send credentialed requests to 127.0.0.1 on behalf of any page the operator has open. What is still not built is an endpoint in the control plane, where one could serve a whole team alongside the session and the audit log. Write tools are deliberately deferred — an agent that can start scans can put traffic across an estate on a misread instruction. See the MCP reference. |
The ordering is deliberate. The REST API comes first because the CLI and the MCP server are both clients of it — building either one first would bake in an unstable contract.
0.5 — Operable at scale
Proved on a cluster, 2026-08-19
The Helm chart was linted, rendered and schema-checked by CI and had never been installed anywhere. Installing it into a k3s node with ingress-nginx and cert-manager took about an hour and found three defects, every one of which made the product unusable at any real hostname:
- A chart helper named a Secret the chart never creates.
cve-ingestcould not start whenever the bundled PostgreSQL was enabled. - The auth client had
localhost:3000compiled into the browser bundle.NEXT_PUBLIC_*is substituted at build time, so every released image told visitors to authenticate against their own machine. Sign-in span forever rather than failing, because the rejected fetch had nothing waiting on it. - The session cookie was set, sent, and never recognised. Better-Auth
prefixes its cookie with
__Secure-over HTTPS; the middleware looked only for the bare name, so a successful sign-in bounced straight back to the login page.
All three share a signature: they exist only when the product is served behind TLS at a real hostname. 721 tests, a kubeconform-validated chart and a CI suite that had grown all week could not see any of them.
The install path is now scripts/k3s-install.sh, with
scripts/k3s-load-images.sh for a cluster with no registry credentials and
scripts/vps-recon.sh for looking at a machine before putting anything on
it. The docker compose quickstart remains unproven — the node used
had no Docker, and installing one beside a running production cluster was
not a change worth making for a test.
| Item | State | Notes |
|---|---|---|
| Shipped | scripts/backup.sh and scripts/restore.sh. One archive holding the dump, its checksum and a fingerprint of the encryption key; the restore refuses a key mismatch rather than leaving every credential silently undecryptable. | |
| Helm chart | Shipped 0.4.0 | External PostgreSQL by default; the chart never generates the encryption key. See ADR-0017. |
/metrics | Shipped | GET /api/v1/metrics, text exposition format, authenticated with a metrics:read API key and scoped to that key's organisation. Exposes what the monitoring guide already asked operators to watch by hand — job run ages above all, since a stopped scheduler produces no error anywhere else. |
| Shipped | Measured at 100 001 hosts, not asserted. Every list sort is an index scan with no sort node: 0.15 ms for last-seen, 0.14 ms for hostname, 0.23 ms for device type, 7.7 ms for a trigram search. No index was added — the existing ones already produce the optimal plan, and an index no query uses is a write cost with no reader. The p99 target remains unverified on production hardware, which is a deployment rather than a laptop. ADR-0023 | |
| Shipped | Upgrading: the Compose and Helm procedures, why migrations never run automatically, what expand-and-contract buys a rolling deploy, when a rollback is a tag change and when it is a restore, and the five checks worth running afterwards. |
1.0 — Enterprise readiness
| Item | State | Notes |
|---|---|---|
| Shipped | Partial. OpenID Connect with PKCE, JIT provisioning and group-to-role mapping ships, exercised against a real Keycloak in CI. SAML does not. | |
| Shipped | Users, Groups, group-to-role, and deprovisioning that ends the sessions somebody already holds. | |
| Audit log surface | Partly shipped | Readable, filterable by subsystem and paginated under Settings. Search, CSV export and configurable retention are not built. |
| Verified air-gap bundle | Planned | A single offline archive — images, CVE feeds, signatures, documentation — proven on an isolated machine rather than assumed to work. |
1.0 means the interfaces are stable and upgrades are supported, not that the
feature list is finished.
Considering
Ideas under analysis. Each will get an ADR before any code is written.
| Idea | Why it is interesting | Why it might be declined |
|---|
Candidates from a field comparison, 2026-08-19
Prompted by looking at L0p4Map, a PyQt6 desktop scanner. It is not a competitor — a single-user desktop tool and a multi-tenant inventory platform do not replace each other — but its feature list named things we do not do, and some are cheap.
Ordered by what they are worth, not by effort.
| Candidate | Why it matters | State here |
|---|---|---|
| nmap and NSE, when the host has nmap | The tool DevOps and network teams already trust, with twenty years of service-version probes and a script library covering protocols we will never write a scanner for. Invoked, never shipped — so no redistribution obligation. See ADR-0024. | Shipped, unreleased — the daemon runs it and attributes the results. No scan-form control yet. |
| The daemon on Windows and macOS | Asked for by sites that will not introduce Docker for one binary. It already cross-compiles to all three targets — what is missing is ARP, which today degrades silently off Linux. See ADR-0025. | Shipped in 0.11.0. All three platforms read the neighbour cache, and CI runs each one. |
Make mac_vendor work, with an OUI database | The pattern engine advertises mac_vendor in its grammar and CLAUDE.md documents it, but pattern.go returns Matched: false unconditionally and nothing populates a vendor from a MAC. Every signature using it fails silently. | Fixed, unreleased. All three IEEE registries ship, longest prefix wins. ADR-0026 |
| Traceroute-based L3 mapping | Our layer-3 topology is built from SNMP routing tables, so an estate that will not give us a community string gets no map. Traceroute groups hosts under their last hop without any credential. | Shipped, unreleased. One trace per scanned range; the adjacent router is recorded with source = 'traceroute'. Needs CAP_NET_RAW, and says so where it is missing. |
| mDNS and NetBIOS name resolution | scan/dns does reverse DNS only. Printers, IoT and Apple devices frequently have no PTR record and answer mDNS; Windows hosts answer NetBIOS. Today they appear unnamed. | Shipped, unreleased. Unicast to the host, only for what DNS could not name. |
| Default-credential device flagging | Flagging the products that ship with a documented factory login turns an inventory entry into a finding, which is the product's whole argument. The note here used to say "banner-grabbing already identifies iLO, Zebra, SATO and XPort" — that was L0p4Map's list, not ours. We had ten core signatures and none of them was such a device. | Shipped, unreleased. The mechanism, plus Grafana, Zabbix and Redis; the YAML field lets an operator add the rest without a rebuild. Nothing is ever tried against a host. |
| TTL-based OS hint | A cheap first guess (Linux/macOS vs Windows vs network device) for hosts that answer ICMP and nothing else — which is most of a client subnet. | Shipped, unreleased. In metadata, never in os_name. |
| Rename a node on the topology | Operators name things their own way, and a map they cannot annotate is one they stop trusting. Cosmetic, and cheap while the topology work is open. | Absent. |
| A live flow view | We collect NetFlow v5/v9 and sFlow and show it aggregated. A per-device live view is more immediate, and immediacy is what a demonstration rewards. | Partial — collection exists, the view does not. |
A correction. This paragraph previously declined full nmap/NSE integration outright, on the grounds that it would mean shipping a restrictively-licensed binary and its update cadence inside an air-gap bundle. The observation was right and the conclusion was wrong: the licence problem is with shipping nmap, not with using it. Invoking the nmap the operator already installed carries no redistribution obligation, needs no OEM licence, and leaves the air-gap bundle exactly as auditable as it is today. It is in the table above, and the reasoning is recorded in ADR-0024.
The honest reading of that comparison. In a ten-minute demonstration to a network engineer, a desktop tool that launches and draws a live graph beats a platform that wants Docker, an enrolled daemon and a scan cycle. Our advantage only appears at the second question — who changed what, on which site, six months ago, and can you prove it to an auditor. That is a real difference and a real weakness at the same time, and the answer is to make first-run work faultlessly rather than to add features.
| Agentless Windows inventory over WinRM | Closes a real gap for mixed estates | Credential handling on Windows is a large surface, and SNMP plus port fingerprinting already identify most Windows hosts | | Passive discovery via SPAN/mirror ports | Finds hosts that never answer a probe | Requires switch configuration most users cannot change, and overlaps with the NetFlow path | | Config-drift detection on network devices | Natural extension of snapshots | Turns Loomscope into a configuration manager, which is a different product | | Multi-instance federation | Some estates genuinely cannot route to one control plane | Doubles the security model. Explicitly out of scope for multi-site, which solves the common case |
Declined
Recorded so the reasoning survives.
| Proposal | Decision |
|---|---|
| gRPC between daemon and control plane | Declined. REST and SSE meet the need, and gRPC would add a second transport, a second schema toolchain and proxy complications for no user-visible gain. ADR-0001 |
| A message broker for job distribution | Declined. PostgreSQL with LISTEN/NOTIFY and job rows covers the volume Loomscope targets. A broker is one more component every operator has to run, back up and upgrade. ADR-0002 |
| Redis for caching or pub/sub | Declined. Same reasoning. PostgreSQL is already required; Redis would be a second stateful dependency. ADR-0002 |
| A privileged daemon container | Declined. Capabilities only, always. The security review is a feature. |
| Telemetry or phone-home, even anonymous | Declined. Loomscope is deployed by people who chose self-hosting specifically to avoid it. |
| Cloud API calls from the daemon | Declined. Cloud discovery runs in the control plane so daemon hosts never hold cloud credentials. |
| A hosted SaaS edition | Declined for the foreseeable future. It would change what the project optimises for. |
Known gaps
Carried from the changelog so this document is honest on its own:
- Scheduled work does not retry across a restart. A failed tick is retried within the same run, with backoff, where the job's interval is long enough for the wait to matter — but a worker that dies mid-retry resumes the normal schedule rather than resuming the attempts. Closing it properly needs a durable queue, which is a dependency decision still open.
- SAML is not implemented. OpenID Connect and SCIM cover the directories that speak either; a provider that only speaks SAML cannot federate.
- There is no public inventory API.
/api/v1/carries the daemon protocol and a few administrative endpoints; the UI talks over tRPC, which is internal and not a stable interface. - There is no
/metricsendpoint. Prometheus support is planned. - Nothing prunes itself. Snapshots and audit entries accumulate indefinitely, so disk grows with inventory size times days retained.
- Automated tests reach a minority of the HTTP routes, and no browser test framework is installed, so the UI is verified only by hand.
- The published OpenAPI specification can drift from the Zod contracts if the pre-commit hook is bypassed.
Influencing this roadmap
Open an issue describing the problem you have, not the feature you want. The most useful contributions to this document have been descriptions of an environment Loomscope handled badly.