CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to LicenseTrack are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Starting with 1.2.0, LicenseTrack follows
[Semantic Versioning](https://semver.org/) as described in
[VERSIONING.md](VERSIONING.md): minor releases carry features and patch
releases carry fixes only. Releases 1.0.0 through 1.1.24 used an earlier
release-train model in which the third number could also carry features.
API stability levels and the breaking-change policy are defined in
[docs/extension-authors/api-stability.md](docs/extension-authors/api-stability.md). Changes that affect stable API
contracts will be called out under a **Breaking** heading in future releases.
## [Unreleased]
## [1.2.1] - 2026-10-29
This fix release for 1.2.0 updates dependencies for security advisories and
fixes native rollback on SELinux systems and portfolio reset. It adds no
database migrations; see the
[upgrade guide](wiki/operations/upgrade.md#upgrading-to-121).
### Fixed
- Portfolio reset no longer fails with a database check-constraint error when
the portfolio contains maintenance records linked to a parent license.
- A confirmation message that appears shortly after another one is no longer
dismissed early by the earlier message's timer.
- On native installs with SELinux enforcing (the default on RHEL, Rocky Linux and
AlmaLinux), restoring a pre-upgrade backup, by `licensetrack rollback` or by the
automatic rollback after a failed upgrade, no longer leaves the service unable
to start. The restore now resets SELinux labels and restores owner-only
permissions on the database file.
### Security
- Updated the build-time `source-map-js` dependency to 1.2.2 for
GHSA-68fv-2mgg-jv7q. It is used only while building the frontend and is not
part of the shipped application.
- Updated Authlib to 1.9.1 (with joserfc 1.7.7 and cryptography 50.0.2) for
CVE-2026-96760, CVE-2026-114615 and related Authlib advisories. LicenseTrack
uses Authlib only for the OIDC authorization-code token exchange and validates
ID tokens with joserfc directly, so the affected Authlib code paths are not
used, but the patched release is now shipped.
- Updated DOMPurify to 3.4.17 for GHSA-cg2g-xh64-v4q7. It is an optional
dependency of the PDF export library; LicenseTrack does not use the affected
`SAFE_FOR_TEMPLATES` mode.
- Updated build and test tooling (Vite 8.3.4, Vitest 5.0.5, undici 8.12.3 and
related patch releases) for Vite, launch-editor, `@vitest/mocker` and undici
advisories. These are used only during development and are not part of the
shipped application.
## [1.2.0] - 2026-10-04
This release adds Registry grouping and generated PO line numbers, unifies
numeric input and line totals, and improves maintenance, CSV round trips and
procurement evidence handling. It is the first release under the Semantic
Versioning policy described in [VERSIONING.md](VERSIONING.md).
Five migrations run automatically on startup. Before upgrading, keep a full
backup of the database, documents and configuration. Review the
[upgrade guide](wiki/operations/upgrade.md#upgrading-to-120) for historical PO
line reconciliation, maintenance price corrections, custom upload extension
lists and integration changes.
### Added
- The sidebar identifies unreleased builds as previews with their branch and
commit. Release-tag builds, and builds without Git information such as Docker
images, show the version number.
- Documents can be `.lic` license files and `.msg` Outlook messages. Existing
native installations using the previous default extension list gain them
automatically on upgrade. Installations with a customized
`ALLOWED_UPLOAD_EXTENSIONS` value need to add `.lic,.msg` to it.
- My Settings → Appearance has a per-user option to show or hide the Portfolio
overview in the sidebar (shown by default).
- Help and the user guide explain how to track SSL/TLS certificates, domain
names and other renewable items with the Other type, Type Description and
Renewable?.
- Hovering or focusing the Expiring or Expired stage on the Licenses page shows
a breakdown, for example "12 expiring · 5 renewal in progress · 1 retiring ·
6 not started". A license with a renewal in progress that is also scheduled
for retirement counts as renewal in progress. The stats API gained
`expiring_breakdown` and `expired_breakdown`.
- A small ⓘ next to License Type, in every form that has one, explains the
difference between Maintenance and Service. Tooltips open on hover and on
keyboard focus.
- PO line numbers: every license and pending-order line with a PO number gets
a generated, read-only PO line number, unique per PO (PO numbers are
compared ignoring case and spacing) and never reused. The number appears in
the API (`poLineNumber`), as a new last **PO Line** column in the license CSV
export, and in the pending-order export. In the app it is shown as
`PO # · LINE n` in License Details, as a Line column on pending orders and in
the conversion screens, and as an optional PO Line column in the Registry
(hidden by default, sortable and filterable). CSV imports of new records keep the file's line
numbers when they are free; the preview shows any number that has to
change. Existing records are numbered once during the upgrade, in creation
order per PO; these historical numbers may not match POs issued before
LicenseTrack, so reconcile them before relying on PO number + line for
matching with other systems.
- Before a new version changes the database schema, LicenseTrack saves a copy of
the database in `pre-upgrade/` next to the database file (newest three kept).
Restarts after a failed upgrade reuse the first copy instead of replacing it.
- Group the Registry by a column, with an optional second level (for example
PO # then Publisher). Drag a column header onto the grouping strip or pick
one from the list. Groups start collapsed and show their line count, line
total per currency and earliest end date; purchase-order groups also show the
Total PO Value and flag a manual PO total that differs from its lines. Saved
views keep the grouping, and CSV export stays flat in grouped order.
### Changed
- A separately tracked maintenance line added next to a license is now
pre-filled with "*product* maintenance" instead of "*product*
maintenance/support". Existing records keep their description.
- Forms show fewer permanent hint lines: background explanations (for example
Supplier Contact, the manual PO total and several settings) moved into ⓘ
tooltips that open on hover or keyboard focus. Hints that prevent a mistake
stay visible.
- The app uses **Maintenance** consistently where it said "Support" or
"Maintenance / Support" (screens, alerts, emails, error messages, Help). For
example, "Support due" is now "Maintenance due" and "Start support renewal"
is now "Start maintenance renewal". Standalone support contracts, such as
premium or third-party support, are recorded as a Service. API fields and
routes, alert type keys and CSV headers are unchanged; older CSV headers are
still accepted on import.
- Help no longer lists the session timeout under My Settings: the session
timeout and minimum password length are server-wide settings.
- Opening a document preview in the Sourcing and Pending Orders document dialogs
collapses the document list and shows a much larger preview; the Add License
document list also collapses while a preview is open. The contract dialog
opens with Linked licenses collapsed (with a count) and its document folders
expanded.
- Internal: updated `brace-expansion` (a dependency of the lint tooling) to
5.0.12 to clear a published advisory. It is not part of the app bundle.
- Added end-to-end tests that run the real frontend against a real backend for
the core write paths.
- The pending-order export's `PO Line #` column now contains the generated PO
line number instead of the row's position.
- Pull requests now include a single-owner audit: every rule a change touches
names its one owner in code, and literal duplicates are caught by guard tests.
- Added a maintainer invariants document listing the rules LicenseTrack
guarantees on every entry point, and a pull request template. The architecture
map identifies the shared calculation, linking, grouping and upload owners.
Shared test cases guard currency-separated totals in reports, Registry groups
and overview totals, including manual PO totals.
- Internal: CSV import and export read their field list from one registry (no
behaviour change). License updates, field patches and import updates share
the notice-date reminder reset rule. Request schemas share the canonical money
validation helper while preserving their field lists, validation timing and
error messages. CSV import also uses the shared included-maintenance type set.
Renewal actions refresh license statistics once through the shared
invalidation group. Conversion defaults reuse the shared custom-field value
map. Removed an unused session-cookie helper and the unused APScheduler
runtime dependency.
- The demo mirrors canonical-number validation and notice handling, including
clearing handling state when the notice date changes.
- Budget owners now receive Maintenance Ending and Maintenance Expired alerts
for the included maintenance on their perpetual, OEM and freeware licenses,
alongside license expiry alerts. These rows are marked "Maintenance" and show
the maintenance dates.
- Updated the frontend test tooling (Vitest 5, jsdom 30.1) and removed automatic
test retries. Form workflow tests use atomic input changes and wait for
asynchronous submission; report workflow tests load their sections before
testing interactions.
- API requests with fields an endpoint doesn't define are logged as a warning,
naming the endpoint and the fields. Set `STRICT_REQUEST_FIELDS=true` to reject
them with a 422 instead; this may become the default in a later release, so
integrations should send only documented fields.
- Line Total is always quantity × unit price. The separate Line Total input is
gone from license, invoice and conversion forms, which show the calculated
value instead, and the Registry has one Line Total column (formerly
Calc. Total). For an invoice line that doesn't divide evenly, enter a precise
or net unit price; for a negotiated PO total, use the manual PO total. In CSV
imports a line total fills a missing unit price or is checked against it; a
Total PO Price column is no longer imported.
- The Registry's Total PO Value shows a lock icon when it comes from a manual PO
total.
### Deprecated
- The `totalPoPrice` license API field. It is still accepted and returned but no
longer used; it will be removed in 1.3.0.
### Fixed
- CSV import warns when a nonblank external reference matches an existing
active license or an earlier row in the file, including records without PO,
contract or dates. Confirm the warning to deliberately reuse a reference.
- Converting merged co-term renewal lines requires an explicit cost-centre
choice when their predecessor licenses had different cost centres, matching
the budget-owner rule. The API enforces the same choice.
- CSV imports require warning acknowledgement when a new row's currency is
blank or missing. The preview identifies the affected rows and the currency
used. Update rows without a currency keep the record's existing currency.
- The Registry's column titles stay on top while the table scrolls. Only the
select-all box stayed; the titles scrolled away with the rows.
- A price cleared on a renewal line stayed cleared only until conversion: Convert
to License refilled the previous term's unit price, maintenance unit price or
maintenance cost. Prices now come from the renewal line only; starting a
renewal still copies the previous price there as a reference.
- Document file pickers offer exactly the file types the server accepts. They
used their own lists: license and procurement pickers left out Excel, Word
and CSV files, and the contract picker offered `.doc`, which the server
refused.
- Linking terms within one request follows one rule in both Set predecessors
and Set next term: a line can't roll into an earlier-starting term, a link
that would close a loop isn't offered, and lines that can't be linked are
shown with the reason instead of being hidden or failing on save.
- The Documents section opens expanded in every add, edit and conversion
dialog. It started collapsed in Add License, Convert All and the single
purchase-order conversion.
- Convert All kept the PO line's estimated total as the license Line Total even
when real unit prices were entered at conversion.
- Subscriptions and SaaS with included maintenance could take their maintenance
cost from an outdated stored total instead of quantity × unit price.
- The Add Maintenance dialog stored the coverage cost as the unit price, so
totals and reports multiplied it by the covered quantity. New records are
correct, and existing records created this way are corrected on upgrade.
- Unit prices are shown with every stored decimal (at least two), so 0.1234 no
longer displays as 0.12; totals keep two decimals. The localized CSV export
keeps unit price decimals too.
- Reading or scrolling inside a focused document preview now counts as activity,
so a long read no longer ends the session.
- Database restores run in the background of the server process, so the app
and `/api/health` keep responding (with status `maintenance`) during a long
restore. Pre-restore safety copies are now kept in a `pre-restore` folder
next to the database (newest three), and copies left next to the database by
older versions are moved there on the next restore.
- Application log messages (such as warnings about unknown request fields) are
no longer silenced after the database upgrade that runs at startup, and keep
following the `LOG_LEVEL` setting.
- Maintenance linking:
- A license can be changed into Maintenance from the edit form by choosing
the license it covers in the same step.
- Choosing "Separately tracked" coverage, in the edit form or with Edit
coverage, offers a quick link to an existing maintenance record.
- Maintenance records that already cover another license stay visible in
link searches, marked "Currently covers", and ask before covering one more.
- The Renewal Workbench's "Record existing support" now lists existing
records to link.
- All maintenance link dialogs search the same fields (including PO number,
contract, dates and the covered license), and show how many retired
records are hidden with an option to show and select them in Link existing,
Edit coverage and the full edit form.
- CSV export → import now preserves manual PO totals,
maintenance pricing (per-unit and free), and which licenses a maintenance
record covers (several allowed, separated by ";"). An exported lifecycle
status is honoured: Legacy stays Legacy, and an active record that expired
recently is no longer turned into Legacy. Update imports can correct an
included maintenance period. The API export gained request and purchase
dates, portal URL, maintenance fields and manual PO total, and its Line Total
column is quantity × unit price.
- One "same PO" rule: shared documents now match licenses whose PO numbers
differ only in case or spacing (listing, downloads, counts and completeness).
Email Supplier's "all matching licenses" includes only lines of the same
purchase and supplier. Document scope labels and delete warnings describe
the actual scope.
- Maintenance coverage has one owner: a license's active maintenance record is
always worked out from its linked records by date, on every path (linking,
CSV import, renewals, the daily hand-over). Linking an older or future record
no longer replaces coverage that is still running; an overlapping new term
takes over when the current one ends. Undoing a link to a renewal that hasn't
started yet now works. Disabling maintenance also removes planned terms.
Changing between perpetual, OEM and freeware keeps included maintenance, and
changing to another type keeps the old included period in history.
- "Renewal in progress" means the same for licenses and maintenance: maintenance
alerts mention it, licenses scheduled for retirement no longer appear in the
maintenance renewal list, and a license with a renewal in progress can't be
retired until the renewal is cancelled. Legacy licenses can't start a
renewal, and a renewed status can't be cleared through a normal edit.
Co-term merges require one license type and keep every covered license
linked to the new maintenance term.
- Numbers with three decimals are no longer multiplied by 1,000 under
comma-decimal number formats (e.g. nl-BE, de-DE). This affected Registry
inline edits, new pending-order lines, and conversion defaults. Stored
values are never re-interpreted, and price inputs keep every decimal
instead of rounding to two. Merging sourcing lines under these formats no
longer turns a combined quantity such as 1500 into 1.5.
- Typed amounts with thousands separators are read correctly, for example
`2,443.00` under the `1,234.50` number format (#82). Input that doesn't match
your number format is refused with a message next to the field instead of
being changed or cleared: under `1.234,50`, `2,443.00` is refused rather than
saved as 2.443. Every screen that takes a price, quantity, total or cost now
uses one number input with one set of rules, shared with the server, and the
API rejects numbers that aren't plain decimals (such as `1,5`).
- Under comma-decimal number formats, a number with a single dot and exactly
three digits after it (e.g. `1.234`) is refused as ambiguous, in number
filters and CSV import too; type `1234` for a whole number or `1,234` for a
decimal.
- **Export Current View (localized)** writes numbers with the same separators
the importer reads, keeping every quantity decimal, so the file imports back
with the same number format. Before, a quantity of 1000 under `1.234,50` was
written as `1.000`, and Swiss formats used a typographic apostrophe.
- CSV import reads an `Item` column as the PO line number. Purchasing exports
(Flexera among them) use `Item` for the line and `Description` for the
product. Earlier versions read `Item` as the software description; a file
that uses `Item` for the product text needs that column mapped to Software
Description (or renamed to `Description`) before import.
- The database models and migrations now describe the same schema, including
the unique OIDC identity index. A test fails the build if they drift apart
again. Document categories fit their column on every database.
- An active user is no longer logged out when their computer's clock runs
behind the server's: session responses now include the seconds remaining,
and the browser measures them on its own clock.
- Procurement conversion:
- Sourcing lines can no longer be added to a pending order that was
converted or cancelled in the meantime.
- Conversion can no longer change a line's currency while the order has a
manual PO total; clear the total first.
- A SaaS portal URL saved on a sourcing or PO line is kept at conversion.
- An invoice uploaded with a conversion is stored together with the new
licenses: if it can't be stored, nothing is converted and you can retry.
- Older conversions whose invoice was lost now say to upload the invoice on
the pending order and retry, instead of retrying automatically.
- Evidence transfers can no longer run twice at the same time.
### Security
- Hardened request validation for signed-in browser sessions, including
sign-out, outbound URL checks for webhooks and SSO discovery, and API tokens
of accounts that must
change their password. Scripts that write through a browser session cookie
(rather than an API token) must now send the `X-LicenseTrack-Request: 1`
header, including when signing out; API-token and bearer requests are
unaffected.
- SSO sign-in rate limiting now counts only failed sign-ins and bounds
in-memory attempt tracking. Successful sign-ins release their tracking
entries. The deployment guide documents `FORWARDED_ALLOW_IPS` for
reverse-proxy setups, and Docker
Compose passes it through.
## [1.1.24] - 2026-09-24
### Added
- Added included-support expiry for perpetual, OEM, and freeware licenses. A
support status (active, expiring, expired) shows as a registry and detail
badge with a **Support due** filter, raises Support Ending and Support Expired
alerts for the in-app list and manager digest, and adds a support row to the
Renewal Workbench. **Start support renewal** creates a maintenance sourcing
line that carries its license into conversion; **Record existing support**
opens the maintenance form. **Edit support** corrects the included period with
an optional cost, and a new **Free (no charge)** pricing basis stores a zero
support cost.
- Added planned maintenance chains of any length on one PO. Maintenance terms
follow maintenance terms only, later terms inherit the supported license, and
each term becomes the active support on its own start date through a daily
hand-over that keeps the previous period in Coverage History.
- Added an opt-in **Renewable?** flag for Service and Other. Renewable records
renew, appear in the workbench, and count toward recurring cost; one-off
records are retired automatically after their end date and raise no expiry
alerts. Other records need a **Type Description**. Both are new Registry
columns and CSV columns (`Renewable`, `Type Description`).
- Added a manual **PO total** to Edit Pending Order for single-currency orders.
It is marked **Override** in the overview, becomes the Total PO Value of every
converted license, and is never spread across lines. License Overview and
Reports note POs whose manual total is not reflected in line-based annual
cost. Pending Orders CSV exports include **PO Total (manual)**.
- Added deep links: the browser address follows the page and selected license,
Back and Forward work, and sign-in returns to the requested address. Opening
a License Details section adds it to the address (`/licenses/123#documents`),
so a shared link opens that section. A new
**Public app URL** setting in Admin > Notifications makes notification emails
link each license to its page.
- Added unhandled notice deadlines to the Renewal Workbench, with an **N**
marker, a **Notice Due** view, and ordering by the earlier of notice and end
date.
- Added per-currency high-value thresholds for the workbench, without currency
conversion. A currency without a threshold is never flagged.
- Added inline editing of Supplier Contact, Budget Owner, Currency, Purchase
Date, and Maintenance / Support Coverage, of Portal URL on SaaS records, and
of Invoice # while a license has a single invoice number.
### Changed
- Renamed the license contact to **Supplier Contact** (whoever you bought from)
and the email action to **Email Supplier**, which greets the supplier. Changing
the supplier on a sourcing request or pending order now asks whether to update
the contact. The API field `contactEmail` and existing CSV headers are
unchanged; `supplier_contact` is accepted as an import alias.
- New installations require PO number, invoice number, and budget owner for
completeness. Upgrades keep their stored requirements.
- Relabelled sourcing and pending-order line estimates **Est. Line Total**, with
a hint when the value differs from quantity x unit price, and the per-line
price input **Line Total**.
- Copy shared fields now includes secondary contacts, copies only filled-in
values, and confirms what it copied. Edit-line and conversion share one
budget-owner rule, and merged coterm lines with different owners require a
choice at conversion.
- The renewal area in License Details can be dismissed until the license is
reopened, and the Term chain moved into History, collapsed by default.
- Sourcing Overview requests start collapsed. Choosing a license type in Add
License applies its default coverage, so a new subscription or SaaS license
starts as Included. The full Edit form offers the Maintenance type only for
existing maintenance records.
- Perpetual, OEM, and freeware records never store an end date on any write
path, and conversion hides the end date for those types instead of offering a
Perpetual checkbox. Existing OEM and freeware end dates are not rewritten;
they are cleared the next time the record is saved in the full Edit form.
- Reports count distinct suppliers in the publisher and supplier summary.
### Fixed
- Fixed manual **Add License** being rejected unless Maintenance / Support
coverage was Included (a 1.1.23 regression, #47).
- Fixed one-year terms of 366 days (leap years or anniversary end dates) being
scaled down in annual cost.
- Fixed the full Edit form dropping all but the first invoice number.
- Kept the included-support period in Coverage History when coverage leaves
Included, and kept perpetual, OEM, and freeware included-support details
entered on manual create.
- Fixed a future-dated maintenance renewal taking over the parent's coverage
before its start date.
- Left blank references and empty periods out of Email Supplier messages.
This release includes five additive migrations that run during normal startup:
`d1e2f3a4b5c6` (renewable Service/Other and type description), `a7b8c9d0e1f2`
(pending-order PO total), `b2c3d4e5f6a8` (per-currency high-value thresholds;
an existing single threshold is copied to the display currency),
`c4d5e6f7a8b0` (public app URL), and `d5e6f7a8b9c1` (the supported license on
maintenance sourcing lines). Stored admin settings are not changed. Existing
Service and Other records start as one-off purchases, so any whose end date has
passed are retired by the first daily job unless marked Renewable first. There
is no breaking stable public API change; new API fields and routes are
additive.
## [1.1.23] - 2026-09-21
### Added
- Added planned term succession for separate future license periods on one purchase
order. Sourcing and pending-order lines can be linked before conversion;
conversion validates their final dates and creates the renewal chain together.
License Details now shows the chain, including upcoming terms. Invoices for
linked multi-term orders default to one license during conversion.
- Added a selectable, collapsible license list when initiating a renewal for
several licenses sharing a PO and end date. Siblings can be excluded, or all
selected and cleared at once.
- Added Secondary Contacts to License Overview columns and CSV exports, a
scheduled-retirement countdown, searchable long filter lists, and pinned
registry controls and column headers while scrolling.
### Changed
- Made license-line forms consistent across manual creation, sourcing, pending
orders, and conversion. Maintenance companions and additional lines retain
their fields; cost centre and budget owner can be set per line, with an
apply-to-all option. Non-expiring types now determine perpetual status.
- Aligned procurement document and overview layouts, moved term-succession
actions into line editors, and reordered License Details fields for review.
- Made Column Categories update a starred default saved view when one is set,
so List View changes appear when License Overview opens. Standardized the
license finance label to **Cost Centre**.
### Fixed
- Corrected included-maintenance details on license creation and maintenance
and additional-line fields in procurement forms. Renewal POs no longer copy
the predecessor's invoice number into the new purchase.
- Kept multi-invoice CSV cells unambiguous on export and import, and aligned
procurement grouping across frontend totals and exports.
- Rejected old human bearer tokens without a recorded login session, restored
near-expiry refresh after inactivity throttling, and scoped cross-tab session
coordination to the current login.
- Made webhook workers claim deliveries atomically and catch up scheduled jobs
missed while the scheduler was busy.
This release includes additive migrations for planned sourcing-line successor
links and webhook delivery claims. They run during normal startup. Users still
holding a sessionless human bearer token must sign in again. There is no
breaking stable public API change.
## [1.1.22] - 2026-09-14
This is a patch update focused on lifecycle correctness, procurement evidence,
CSV round trips, background jobs, and reliable session expiry and logout.
### Fixed
- Fixed concurrent existing-successor linking allowing conflicting renewal links,
and protected pending-renewal lifecycle state from ordinary edits.
- Validated established renewal terms when editing either side of a renewal chain
and when importing successors, including coterm predecessor relationships.
- Fixed existing maintenance-successor linking to activate coverage for every
parent and require a maintenance successor. Unlinking now restores the previous
parent coverage, relationships, and history, with conflict checks when coverage
has changed since linking.
- Protected maintenance creation from lifecycle-field overrides, allowed valid
edits to retired unlinked maintenance, and prevented unchanged parent selections
from reactivating maintenance. Date edits now normalize scheduled-retirement state.
- Fixed repeated coterm merges losing earlier predecessor relationships and quote
evidence. Shared quotes remain available to unmerged sibling lines; Single
line-specific evidence must be removed or changed to Shared before merging.
- Preserved support fallback fields during pending-order conversion and avoided
scheduling quote-evidence transfers for sourcing requests without quote documents.
- Fixed purchase-order total matching and Registry CSV grouping to use normalized
procurement identity, keeping separate pending orders isolated even when they
reuse a PO number. Currency changes now clear incompatible standalone PO overrides.
- Fixed CSV completeness checks omitting shared documents, preserved multiple
invoice identifiers without splitting identifiers containing commas, and kept
bundled support fields synchronized during CSV updates.
- Fixed secondary-contact formatting to accept both stored lists and text values.
- Prevented deleted user IDs from being reused by newly created accounts.
- Fixed daily notification and backup jobs being skipped when background work
crossed their scheduled time, and released webhook database transactions between
deliveries to avoid holding them open during network requests.
- Fixed session expiry, refresh coordination, and delayed logout or refresh responses
interfering with a later login. Each login now has an independently revocable
session with server-enforced sliding expiry; logout and inactivity expiry revoke
the current session while preserving other device sessions.
- Coordinated protected requests with session refresh, shared activity across browser
tabs, counted panel scrolling as activity, and exposed the configured timeout to
non-admin users. Split deployments refresh from each tab's actual bearer-token expiry.
- Cleared cached application data on logout and expanded regression coverage for
lifecycle, procurement, CSV, background-job, and authentication fixes.
### Documentation
- Refreshed the public website and demo presentation, added an illustrated demo
walkthrough, and reorganized operator guidance for evaluation, prerequisites,
first login, CSV imports, and renewal workflows with focused reference pages.
This release includes database migrations to prevent user ID reuse, retain
maintenance state for undoing existing-successor links, and track independently
revocable login sessions. Existing browser sessions require a fresh sign-in after
upgrade. Downgrading the session migration also requires users to sign in again.
No breaking public API change is included in this release.
## [1.1.21] - 2026-09-10
### Added
- Added authenticated PDF previews to contract details, with a wide split layout,
independent document and preview areas, and responsive stacking on narrow screens.
- Added an independent global renewal-action window. Administrators can decide how
many days before expiry initiation and existing-successor linking become available;
installations that do not configure it retain the existing expiry-alert-window
behavior.
- Added explicit Shared or Single scope controls for Quote, Purchase Order, Invoice,
EULA, and Entitlement attachments throughout manual creation, maintenance creation,
sourcing, pending orders, and conversion.
### Changed
- Expiring licenses with an actual linked Upcoming successor now show a steel
**Renews in X days** badge, or **Renews today**, based on successor coverage while
retaining the predecessor's expiration state and any coverage-gap warning.
- Renewal bundles remain limited to eligible licenses with the same PO number and end
date, but the workbench now resolves membership from the complete candidate set
instead of only the currently visible rows.
- Collapsing the shared Documents controls no longer closes or hides the selected
staged-file or stored-document preview.
- Newly uploaded shared evidence for direct/manual licenses can be reused by licenses
with the same normalized PO number. Pending-order identity remains authoritative,
so unrelated pending orders never share evidence merely because their PO numbers
match. Existing document ownership is not migrated or backfilled.
### Fixed
- Fixed overdue renewal views omitting expired licenses whose renewal procurement is
already in progress.
- Fixed linked-renewal badges being omitted when normalized API records expose the
successor through renewal-chain fields other than the legacy lookup shape.
- Fixed license details and Registry CSV exports dropping valid zero or negative
calculated totals.
- Fixed sourcing quote evidence being unavailable from pending-order conversion.
- Fixed pending-order invoice uploads losing their pending-order scope when shared PO
metadata was also present, which could expose evidence to a different pending order
using the same PO number.
- Fixed CSV updates defaulting omitted currencies, accepting invalid term dates,
retaining handled state for changed notice dates, and leaving maintenance mirrors stale.
- Fixed PO totals and overrides crossing separate procurement groups that reuse a PO number,
including frontend cache, sorting, detail, overview, and Registry CSV behavior.
- Fixed linked maintenance costs being displayed in the parent license's currency.
- Fixed purchase-line conversion dropping previously entered commercial and ownership data.
- Fixed Escape saving inline edits instead of cancelling them in Registry and procurement tables.
- Fixed successful documents remaining hidden after a later file in the same upload failed.
This release includes additive migrations for procurement-document scope metadata and
the renewal-action setting. It does not move, reassign, or backfill existing documents,
and it introduces no breaking public API change.
## [1.1.20] - 2026-09-08
This is a minor fix release for a database migration failure encountered when
upgrading from 1.1.17 to 1.1.19.
### Fixed
- Fixed the scheduled-retirement migration attempting to reactivate retired
maintenance records without a parent license or the explicit legacy-unlinked
exception when their end date was today or later. Those records were valid
while retired, but reactivating them violated the
`ck_license_maintenance_has_parent` database constraint and stopped the upgrade.
The migration now leaves these records retired while continuing to schedule
retirement for eligible records, preserving parent links and legacy flags.
- Added regression coverage for the 1.1.17 upgrade path, date boundaries,
database integrity, and migration downgrade/re-upgrade behavior.
This release corrects an existing migration; it adds no new migration revision,
configuration requirement, or breaking public API change. Operators affected by
the failure should use 1.1.20 through the normal upgrade procedure. Installations
already running 1.1.19 can upgrade normally to 1.1.20.
## [1.1.19] - 2026-09-07
### Added
- Added a deeper License Overview sidebar that separates current coverage,
planned changes, and historical records, including renewal-in-progress and
scheduled-retirement counts.
- Added coverage urgency for pending renewals: records retain their Expiring or
Expired coverage status while also showing the Pending Renewal workflow state,
filters, counts, and urgency styling.
- Added retirement-scheduled and renewal-notifications-off badges to license
details.
- Added expand-all and collapse-all controls for sourcing and pending-order
tables, with clearer renewal context and previous-license descriptions in
expanded line items.
### Changed
- Changed existing-successor matching to use the normalized publisher as the
required identity while allowing other entitlement details to differ, with
explicit successor term and license-type eligibility checks.
- Kept pending renewals in current coverage and recurring reporting until their
current term ends, while preserving the separate renewal workflow state.
- Streamlined sourcing and pending-order workspaces with clearer inline-edit
controls, conditional currency display, multi-row expansion, and an explicit
path to add a missing purchase-order number before conversion.
### Fixed
- Made single and bundled renewal initiation reserve lifecycle transitions
atomically, preventing duplicate concurrent renewal workflows.
- Coordinated scheduler and background database jobs with restore maintenance so
database replacement waits for active work to drain.
- Allowed manual retry of escalated evidence transfers and repaired missing or
stale copied quote evidence during retry.
- Restricted renewal initiation to records that are actually Expiring or
Expired, and kept pending-renewal alerts aligned with the underlying coverage
urgency.
No database migration, configuration change, or breaking public API change is
included in this release.
## [1.1.18] - 2026-09-06
### Added
- Added reusable contact suggestions for publisher contacts, budget owners,
secondary contacts, and other people fields across license, sourcing,
pending-order, and conversion forms, backed by an authenticated reference-data
endpoint and matching demo-mode behavior.
- Added inline editing for sourcing line items and pending-order overview, line,
purchase-order, invoice, supplier, and date details, plus sourcing-quote preview
from the pending-order workspace.
- Added an Upcoming count to license navigation and the ability to schedule an
active license for automatic retirement when its current term ends.
- Added per-custom-field renewal behavior and sourcing-form visibility controls.
- Added categorized document staging during license creation and pending-order
conversion, including an explicit choice to share procurement evidence across
the created licenses or attach it to only one license.
### Changed
- Unified license and procurement forms around shared defaults, standard field
sections, custom-field sections, secondary-contact normalization, sourcing
payload builders, and custom-field definition queries.
- Consolidated staged and stored document preview metadata, attachment state,
category handling, and modal layout while preserving the existing storage and
evidence-transfer boundaries.
- Suppressed renewal attention and notification alerts as soon as a successor is
secured, while retaining the predecessor's date-based coverage state.
### Fixed
- Updated `fflate` to 0.8.3 to prevent malformed ZIP64 archives from causing an
infinite parsing loop, and updated the ESLint `@humanfs/node` dependency to
0.16.8 so recursive development-tool copies do not follow symlinked files
outside their source tree.
- Normalized blank procurement milestone dates to null during license writes so
clearing a date no longer causes validation failures.
- Kept sourcing supplier suggestions visible above editable rows and clarified
when sourcing inline-edit mode is active.
This release includes three Alembic migrations. They replace the custom-field
renewal carry-forward flag with `clear`, `copy`, and `prompt` behavior; add the
scheduled-retirement flag and preserve future-dated retired records as scheduled;
and add sourcing-form visibility for custom fields, enabled by default. Downgrade
maps `copy` back to the legacy carry-forward flag, restores scheduled records to
retired, and removes the new sourcing visibility field. No configuration change
or breaking public API change is required.
## [1.1.17] - 2026-09-02
### Added
- Added complete license-field and section-aware custom-field capture throughout
sourcing and pending-order intake, conversion, and renewal workflows, including
optional custom-field carry-forward on renewal.
### Changed
- Unified procurement intake and conversion forms around shared field sections
and a consistent document preview workspace for staged and stored files.
- Made backend-provided lifecycle and expiration status authoritative across the
registry, license details, renewal actions, reports, and exports.
### Fixed
- Renewed active sessions before expiry so users working in the application are
no longer logged out after a fixed interval; the configured inactivity timeout
still applies.
- Preserved a renewed predecessor's own date-based coverage state until its term
ends, including overlapping and upcoming successor messaging.
- Kept License Overview Attention-banner dismissals for the authenticated browser
session and cleared them on logout or session expiry.
- Improved document preview compatibility while restricting rendered previews
to generated blob URLs, and replaced backtracking currency-affix parsing with
deterministic handling of untrusted values.
- Hardened staged-field migrations, backup restore coverage, and targeted
sourcing-order reloads.
This release includes an Alembic migration that adds staged license fields and
custom-field values to sourcing items plus the custom-field renewal carry-forward
flag. Downgrade removes those additions. No configuration change or breaking
public API change is required.
## [1.1.16] - 2026-08-30
### Fixed
- Invalidated cached global settings after a successful database restore so
subsequent requests cannot continue using values from the replaced database.
### Changed
- Removed three inert persisted settings fields while retaining all active
notification, manager-email, appearance, locale, security, backup, and
integration settings contracts.
- Consolidated the shared prepared-item execution used by single and batch
pending-order conversion while preserving the early SQLite write lock,
transaction ordering, partial-failure behavior, audit events, response
contracts, and evidence-transfer recovery.
- Split detailed-report construction into focused lifecycle-spend,
procurement, recurring-forecast, portfolio, renewal, and perpetual-
maintenance builders without changing response shape or currency semantics.
- Aligned canonical and alias lookup and alias-addition validation for
organizations and cost centres while retaining their distinct merge,
synchronization, and access rules.
- Shared native and mapped CSV row assembly while preserving localized
parsing, preview values, warnings, nested transactions, reference updates,
maintenance inference, and row-level errors.
- Documented that license statistics and detailed reporting intentionally keep
separate recurring-cost ownership because their unpriced-value semantics
differ.
This release includes an Alembic migration that removes only the unused
per-user `notification_days` and `manager_email` columns and the unused global
`auth_method` column. Downgrade recreates those inert columns with their
historical defaults. No public API, configuration, or visible product workflow
is removed.
## [1.1.15] - 2026-08-30
### Changed
- Consolidated frontend routing, license creation, authenticated downloads,
query invalidation, CSV preview rules, and suggestion review around their
canonical hooks, API helpers, and components.
- Moved duplicated backend route and workflow logic into the owning license,
sourcing, pending-order, import, document-processing, user, and plugin
services.
- Unified internal storage, custom-field, reference-data, lifecycle,
reporting, and notification operations while preserving existing public
API and operator behavior.
- Kept registry column defaults in the frontend while retaining persisted user
overrides and existing settings contracts.
This release introduces no database migration or configuration change.
## [1.1.14] - 2026-08-26
### Added
- Added filtered detailed-report APIs and complete, formula-safe CSV and
paginated PDF exports with native-currency totals.
- Added persistent notification-run status for successful, partial, blocked,
failed, skipped, and no-work outcomes.
- Added stable OIDC identity binding and per-user session invalidation.
- Added optional atomic line-item creation to `POST /api/pending-orders` while
preserving header-only requests.
- Added sourcing-quote provenance and retired-license context to procurement
history.
### Changed
- Unified report cards, forecasts, detailed views, CSV, and PDF around the same
filtered, currency-safe calculations.
- Improved notification classification, delivery outcomes, loading errors, and
retry guidance across scheduled and manual runs.
- Enforced backend session timeouts, rotated tokens after password changes, and
invalidated sessions after security-sensitive account changes.
- Expanded audit coverage for downloads, exports, folders, mappings,
notifications, webhooks, backups, and restores.
- Prefilled standard renewals with an editable one-year successor term and
required a budget owner before renewal initiation.
- Aligned Renewal Workbench bundle eligibility with License Details and
restricted existing-successor links to matching entitlements with advancing
terms.
- Preserved individual line values in coterm merges, including mixed unit
prices, while continuing to reject mixed currencies and license types.
### Fixed
- Corrected reports for mixed currencies, shared procurement and maintenance
costs, invalid prices, and undated recurring records.
- Strengthened notification recipient validation and prevented repeated
automatic retries after an unsuccessful daily attempt.
- Enforced forced-password changes at the API boundary, rejected inactive local
logins, and tightened OIDC issuer, audience, email, and subject validation.
- Made database restore maintenance-safe with archive and schema validation,
pre-swap migrations, recovery snapshots, and reliable outcome auditing.
- Prevented department-scoped Viewers from accessing shared contracts or
procurement evidence connected to an unassigned department.
- Improved contract authorization, validation, legacy matching, document error
handling, and synchronized document state.
- Kept quote, purchase-order, and invoice evidence on the correct scope and
improved upload rollback, deletion cleanup, and missing-file handling.
- Strengthened CSV import parsing, header and mapping validation, custom-field
handling, and duplicate detection.
- Isolated purchase-order totals by PO number and currency and made exports
deterministic.
- Enforced unique organization and cost-centre names and aliases, with an
upgrade check for existing conflicts.
- Reduced reference-data query overhead and made custom-field reordering atomic.
- Prevented concurrent sourcing conversions from creating duplicate pending
orders or licenses.
- Scoped renewal cancellation correctly and stopped upcoming licenses from
generating expiry notifications.
- Corrected shared-maintenance renewal, coverage history access and refresh,
and maintenance-cost calculations.
- Improved final-line pending-order deletion, partial document-upload handling,
and procurement-evidence transfer recovery.
- Kept procurement history navigation and links usable for filtered, converted,
and retired records.
## [1.1.13] - 2026-08-25
### Added
- Added **Link existing successor** to the renewal workflow so a pre-purchased
upcoming license under the same purchase order can become the next term
without creating a duplicate purchase. The adopted license follows the
standard renewal chain, procurement trail, lifecycle transition, reference
alias, and unlink behavior.
### Changed
- Unified new managed document storage under an `attachments/` hierarchy keyed
by immutable license, pending-order, procurement-bundle, sourcing-request,
and contract IDs instead of using purchase-order numbers as physical folder
names. Legacy stored paths remain readable and supported by backup, restore,
and portfolio reset workflows.
- Classified Quote, Purchase Order, and Invoice uploads consistently as
procurement evidence even when the attached license does not yet have a PO
number.
### Fixed
- Made the **Docs** column selectable from License Overview and per-user
settings, renamed **Visible Categories** to **Column Categories**, and kept
the selector within the viewport when the attention banner lowers the toolbar.
- Required document categories now count toward completeness only when the
managed file is available. Missing or unavailable files retain their
metadata and availability counts but no longer make a license appear
complete in the Registry, reports, notifications, exports, or Renewal
Workbench.
- Rejected newly created or edited terms whose end date precedes their start
date across manual entry, inline editing, CSV import, procurement conversion,
maintenance, and renewal workflows. Existing invalid historical records
remain readable and repairable instead of breaking the Registry response.
- Stopped classifying recurring licenses without an end date as perpetual.
They remain active but incomplete until an end date is supplied, while true
non-expiring license types retain perpetual status.
- Rejected explicit null values for required license fields and safely
normalized null values for clearable text fields, preventing database errors
during full and inline updates.
- Scoped bulk selection to the displayed page, cleared hidden selections when
filters or pagination change, and identified the selected licenses in the
delete confirmation.
- Reset and clamped Registry pagination after searches, filters, saved-view
changes, and page-size changes so a valid filtered result cannot appear as an
empty out-of-range page.
- Aligned Registry status filters with portfolio counts: **Active** no longer
includes expiring licenses, and **Incomplete** no longer includes
completeness-exempt records.
- Removed the redundant all-custom-field-values request from License Overview
and built the custom-field map from values already embedded in the license
registry response.
- Kept core license loading independent from auxiliary statistics, sourcing,
pending-order, contract, and custom-field-definition failures. The Registry
retains last-known supporting data, shows unavailable pipeline counts as
unknown instead of zero, offers a focused retry, and applies stable license
ID ordering before offset pagination.
## [1.1.12] - 2026-08-23
### Added
- Added an explicit **Import as legacy unlinked maintenance** exception for
maintenance purchases whose original perpetual, OEM, or freeware parent is
unavailable. Preview supports row-level and bulk selection, requires warning
acknowledgement, marks the imported record for follow-up, and lets Editors
and Admins establish a normal parent relationship later from License Details.
- Added upgrade migrations that restore missing foreign-key delete behavior for
license relationships and procurement documents, persist invoice-evidence
retry requirements, and enforce the explicit legacy-unlinked maintenance
state without silently rewriting invalid existing relationships.
### Changed
- Updated supported backend and frontend dependencies to their latest
compatible maintenance releases.
- License Overview sorting now follows each column's displayed meaning,
including PO-wide totals, calculated values, lifecycle-aware expiration,
localized labels, timestamps, and typed custom fields. Unsupported columns no
longer advertise sorting, and filters now cover additional displayed fields
and custom-field values consistently.
- Renewal Workbench estimated annual value now annualizes multi-year term cost.
Renewal sourcing, coterm merges, and pending-order conversion preserve an
explicit maintenance/support classification and apply the correct default
when older recurring records do not have one stored.
- Pending-order batch conversion now validates a complete one-to-one set of all
eligible lines before taking the conversion lock or creating licenses.
### Fixed
- Kept PO-wide totals and manual overrides scoped by both PO number and
currency, preventing reused mixed-currency PO numbers from combining or
relabelling monetary values in the Registry and reports.
- Aligned sourcing and pending-order overview totals and sorting with the
canonical procurement line total, including separately priced support and
preserving currency groups instead of combining unlike currencies.
- Hardened local sourcing quote previews against DOM-based XSS by encoding
preview URLs, rejecting conflicting MIME types, and sandboxing PDF previews.
- Made post-conversion quote and invoice evidence transfer durable and
idempotent across phase commits, status-update failures, scheduled retries,
and missing required invoice files. A failed transfer remains recoverable
without deleting already committed evidence or reconverting licenses.
- Preserved maintenance parent links, active-parent mirrors, coverage state,
and legacy-unlinked state through ordinary edits, linking, disabling,
retirement, renewal successors, conversion responses, and parent deletion.
- Prevented CSV import from creating an inferred maintenance child when its
same-file parent is skipped, kept warning and audit counts aligned with the
effective rows, honored the selected date format for mapped custom fields,
and refreshed all affected Registry, renewal, report, notification, and
reference-data caches after import.
- Allowed a license referenced only by cancelled renewal sourcing history to be
deleted while retaining that history with the deleted predecessor reference
removed. Active renewal/procurement relationships continue to block deletion.
- Fixed sourcing-request supplier changes so refreshed parent and line values
are persisted and audited correctly.
- Fixed Registry zero-value totals, missing-value ordering, creator filters,
quantity and notice-date filters, and stable ascending/descending ordering.
## [1.1.11] - 2026-08-20
### Added
- Added canonical Companies and Departments / Cost Centres with stable IDs,
aliases, publisher/supplier roles, active status, usage counts, safe rename,
merge, and delete workflows under Admin > Settings > Data Management. The
upgrade migration backfills existing publisher, supplier, and cost-centre
text without discarding the compatibility display fields used by existing
integrations.
- Added reference-aware publisher, supplier, and cost-centre selectors across
license, contract, user-access, sourcing, pending-order, conversion, and
maintenance workflows. Existing names and aliases resolve to the canonical
record while new values can be created through supported editor workflows.
- Added CSV reference-data review. Preview groups candidate organizations and
cost centres, reuses exact names and aliases, and requires explicit decisions
for possible duplicates or inactive references. Decisions are revalidated at
execution time, and new references are created only for successful rows.
- Added immutable maintenance coverage history. When included support or an
active maintenance record is replaced, LicenseTrack snapshots the prior
period and exposes it from License Details alongside the current period.
- Added **Perpetual Licenses & Maintenance** and **Purchase Order Value
Tracker** report sections for reviewing acquisition value, included or
separately tracked support, manual PO overrides, line totals, and per-PO
differences without combining currencies.
- Added request-level sourcing editing for supplier, contact, notes, and all
open line items in one validated save. Converted and cancelled lines remain
read-only. Newly attached quote files can be previewed in a split or expanded
view before the sourcing request is saved.
### Changed
- Reorganized Admin Settings into General, Data Management, Integrations, and
Operations. Reference data, license configuration, and import configuration
now live together under Data Management.
- CSV preview now supports searchable maintenance-parent repair, configurable
column visibility, condensed responsive layouts, collapsible reference
review, and bulk decisions for unresolved possible duplicates.
- Perpetual, OEM, and freeware imports no longer become legacy merely because
an included-support end date has passed. Expired included support is instead
surfaced as an acknowledgement-required warning.
- Registry price filters accept the user's localized number format, maintenance
coverage can be filtered explicitly, and the selected license row remains
highlighted while its detail panel is open.
- Procurement forms now resolve canonical organizations and cost centres,
preserve renewal-parent and purchase-date defaults during conversion, and
keep quote actions at the owning request or order level rather than repeating
them on each child line.
- Stable API responses may include additive `publisherId`, `supplierId`, and
`costCentreId` fields. Existing name-based request fields remain supported;
when an ID is present it is authoritative and the returned name is canonical.
### Fixed
- Refreshed license and reference-data query state after imports and
procurement mutations so newly created parents, aliases, assignments, and
canonical names appear without a manual reload.
- Preserved maintenance, renewal, supplier, purchase-date, and canonical
reference relationships through direct writes, imports, sourcing updates,
pending-order conversion, maintenance creation, and renewal successors.
- Made sourcing-request line updates atomic and prevented edits to converted or
cancelled lines while retaining their historical values.
- Hardened reference merges, deactivation, role changes, viewer department
assignments, CSV execution preflight, demo behavior, and large reference-list
rendering against stale or conflicting decisions.
## [1.1.10] - 2026-08-16
### Added
- Added a shared manual Total PO Value override for purchase orders whose
invoice provides only one total without usable line-level pricing. Editors
and admins can set or clear the override from any License Details record in
the PO; the value is synchronized across every license sharing that PO
number and is included in registry CSV exports.
- Added separate Reports totals for Spend by License and Spend by PO Value,
plus their Difference. Manual PO overrides are counted once in PO-value
spend without being distributed into license-level breakdowns or forecasts.
### Changed
- Report sections now start collapsed, retain their expanded state for the
browser session, and use the same compact section behavior as Admin and User
Settings.
- Added compact search and result counts to detailed recurring-cost and
publisher/supplier report tables, along with clearer filter status and reset
behavior.
- Clarified that the importable legacy stored PO price is compatibility data,
not the manual PO override or an active Total PO Value calculation.
### Fixed
- Kept shared PO overrides consistent when licenses are created, imported,
converted, renewed, or moved between PO numbers, including inheritance from
an existing PO and correct clearing when a license leaves a grouped PO.
- Fixed localized PO override entry so grouped values remain stable after the
field loses focus.
- Preserved the pending-order conversion conflict guard while inheriting an
existing PO override, so concurrent conversion attempts still reject the
losing request with a conflict instead of an internal error.
- Prevented report PDF export while a custom date range is invalid and removed
the duplicate date-range error message.
## [1.1.9] - 2026-08-14
### Added
- Added the backend maintenance-link foundation that lets one maintenance
license be associated with multiple perpetual, OEM, or freeware parent
licenses while preserving the existing primary-parent compatibility field.
- Added backend workflows to create maintenance records with multiple parent
licenses and link an existing maintenance record to another eligible parent
without retiring shared maintenance when one parent disables tracking.
- Added the License Details maintenance workflow for linking an existing
maintenance/support record to another parent from a compact searchable list,
alongside the existing create-new path.
- Added CSV import row remediation for maintenance records so an unresolved
maintenance parent can be selected from existing eligible license records
during preview and applied during import.
### Fixed
- Fixed demo bundled included support propagation so subscription and SaaS
records derive support dates and support cost from their subscription term and
acquisition value without double-counting procurement totals.
- Fixed CSV import handling for perpetual, OEM, and freeware rows with included
support so imported coverage dates and explicit support cost are preserved; if
no support cost is supplied, LicenseTrack defaults from the line total and
warns the operator to verify the value.
## [1.1.8] - 2026-08-14
### Changed
- Expanded Flexera import normalization for license metrics and maintenance
flags. Common Flexera metric labels now map to LicenseTrack metrics, while
Custom Metric, Unknown, and Other land on Other / Unknown instead of blocking
import. Flexera-style "includes maintenance" boolean columns can now map to
included support coverage.
- Hardened CSV imports for files that were exported from LicenseTrack, adjusted
in Excel, and saved again, so common spreadsheet rewrites do not prevent
re-import.
- Updated Reports recurring-cost calculations so multi-year subscriptions,
SaaS, maintenance, and paid included-support records are annualized for
forecast baselines and allocated by overlapping days when a report date range
is selected.
- Changed Sourcing Overview active rows to start expanded and allow each
request to be collapsed independently.
- Showed Created as a date-only value in the License Overview table while
keeping the full timestamp in License Details history.
- Reordered License Details quantities to show Purchase Quantity, Quantity per
Unit, then Effective Quantity.
- Allowed included support coverage on subscription and SaaS licenses. For
those bundled-support records, LicenseTrack derives support dates from the
subscription dates and support cost from the acquisition total, hides the
derived inputs in license, sourcing, and pending-order forms, and avoids
double-counting the bundled cost in procurement totals.
## [1.1.7] - 2026-08-13
### Added
- Added native **Service** and **Other** license types for implementation
services, purchase-adjacent costs, and uncommon external purchase types that
should remain in the registry without being treated as renewable entitlement
lines.
- Added authenticated PDF preview from License Details documents. Existing PDF
files can now be previewed in a side pane while the details panel remains
usable, and the original download action is unchanged.
- Added native Quantity per Unit and derived Effective Quantity support across
licenses, CSV imports, registry exports, details, manual creation, and
pending-order conversion. Purchase Quantity remains the commercial count used
for cost calculations, while Effective Quantity represents entitlement scale.
### Changed
- Expanded Flexera purchase-type handling so common values such as Software
Subscription, Software Maintenance, Software Baseline, Software, and Service
map to the closest LicenseTrack license type during import.
- Made the Reports department and cost-centre filter searchable and scrollable
for large portfolios.
- Capped the License Overview attention banner at six visible warnings and
remeasured the virtual registry after dismissing it.
- Displayed request date and purchase date as date-only values in License
Details to reduce timestamp clutter.
### Fixed
- Prevented weak fallback import aliases such as `Item` from overriding a real
Software Description column, and kept duplicate recognized columns available
for manual mapping instead of silently hiding them.
- Excluded legacy records from report forecast calculations when retired and
legacy records are not included.
- Renamed the Full Data CSV `total_po_price` header to `total_po_value` so the
export label matches the stored portfolio meaning.
## [1.1.6] - 2026-08-12
### Added
- Added license **Secondary Contacts** beside the budget owner. These contacts
are stored on the license, editable from License Details, and copied as CC
recipients on budget-owner renewal emails when SMTP notifications are enabled.
- Added CSV import support for secondary contacts, including repeatable mapped
email columns such as application owner or technical owner fields from
external exports.
- Added native and mapped CSV import support for request date, purchase date,
procurement reference, and parent LT Ref fields so imported legacy records can
match the current application data model more closely.
### Changed
- Expanded the native CSV template and import preview to show the newer
procurement and contact fields operators are likely to reconcile during a
first portfolio load.
- Capped the License Overview warning list with an internal scrollbar so large
first-import warning batches no longer take over the whole page.
### Fixed
- Hardened CSV quantity parsing for Flexera-style exports that contain both
purchase quantity and quantity-per-unit values. Quantity-per-unit headers are
no longer treated as the purchased license quantity.
- Treated far-future dates such as `1-1-2099` as perpetual license indicators
without blocking import.
- Shared the declared import date format with custom date-field validation so
native and mapped imports parse operator-selected date formats consistently.
- Preserved localized numeric parsing and update-on-LT-Ref behavior while
applying the new import fields.
## [1.1.5] - 2026-08-09
### Added
- Added a procurement reference field to sourcing-to-pending-order workflows so
teams can track internal request or approval numbers before a formal PO
number exists.
- Added paginated sourcing and pending-order history tables with compact row
action menus for evidence download and deletion.
- Exposed sourcing quote evidence from pending-order rows and pending-order
history so quotes remain reachable after a request leaves active sourcing.
### Changed
- Pending orders can now be created before the real PO number is known, while
final conversion to active licenses still requires an actual PO number.
- Sourcing and pending-order row actions now favor the primary Convert action
and move edit, evidence, and cancel/delete actions into a consistent menu.
- Evidence action labels now use filenames directly, making multiple uploaded
quote or PO documents easier to distinguish.
### Fixed
- Included sourcing quote documents in pending-order API responses so pending
order views can offer quote download and delete actions.
- Refreshed frontend audited transitive dependencies and aligned demo/unit tests
with the current procurement conversion forms.
## [1.1.4] - 2026-08-04
### Added
- Standardized exported record identifiers as **Sourcing Request ID**,
**Sourcing Line ID**, **Pending Order ID**, **Pending Order Line ID**, and
**License Record ID**. License Details now shows the exact License Record ID
alongside creator and timestamp history.
- Expanded direct multi-license entry so every eligible line can configure
included or separately tracked maintenance. Quote, purchase-order, and
invoice evidence uploaded with a multi-license batch is shared across that
batch without using PO-number text as the sharing key.
### Fixed
- Made direct multi-license creation atomic and protected it from repeated
submission. A failed row or audit write creates no licenses, while a
post-create attachment failure is reported separately so operators can retry
the document without recreating the batch.
- Removed managed license-document files only after a successful single or bulk
license deletion commit, while preserving procurement evidence that is still
shared by surviving licenses.
- Included incomplete-license items in manager-digest eligibility and preserved
notification and backup hour `0` as midnight.
- Added structured audit events for contract-document uploads/deletions and
changed license custom-field values, without adding no-op custom-field events.
- Accepted a file payload exactly equal to the configured upload maximum while
continuing to reject larger payloads and excessively large multipart
requests.
- Corrected gray-theme contrast, programmatic form-label associations, and
desktop keyboard isolation so activating a contract tile's delete control no
longer opens the contract.
- Preserved localized number formatting for added manual license lines and gave
those lines the same maintenance/support choices as the first line.
- Refreshed supported backend, frontend, and GitHub Actions dependencies,
including current security fixes for audited transitive packages.
## [1.1.3] - 2026-07-26
### Added
- Added an optional license `notice_date` for manually tracked contractual
notice deadlines. Notice dates can be shown, edited, imported, exported,
included in completeness goals, and warned on when they fall after the
license end date.
- Added a separate global notice-deadline alert window. Notice deadline
reminders appear in notifications and manager digests, while expiry emails
continue to use the budget-owner recipient flow.
- Added a `Mark Handled` action for notice deadlines. Handled notice dates are
excluded from in-app notice alerts and manager digests, and the handled state
clears automatically when the notice date changes.
### Fixed
- Enforced real browser-session logout for the inactivity timeout so an expired
frontend session cannot be immediately restored from the still-valid session
cookie.
- Hardened login throttling so a successful username login no longer clears
the source-IP password-spray limiter, and active limiter entries are capped
after expired entries are pruned.
- Prevented stale document refreshes from replacing the currently selected
license's document list after uploads, deletes, or processing-review actions
resolve out of order.
- Honored the configured expiry alert window consistently across Registry
statistics, license responses, exports, reports, renewal and maintenance
responses, pending-order conversion responses, contracts, and notifications.
- Treated contract numbers as case-insensitive contract identities for contract
create/update validation, license linking, linked-license counts, and viewer
contract access, while returning clear conflicts for pre-existing duplicate
contract records instead of server errors.
- Scoped `GET /api/licenses/departments` to the caller's visible license
departments so viewers cannot enumerate departments outside their assignment.
- Parsed report `YYYY-MM-DD` values as local calendar dates in date-range
filters and the renewal calendar, avoiding off-by-one quarter results in
negative UTC offsets.
- Deduplicated exact duplicate viewer department assignments before saving so
repeated values no longer trigger a database integrity error, while preserving
manually distinct casing such as `ART` and `art`.
## [1.1.2] - 2026-07-25
### Fixed
- Aligned License Details renewal cancellation with sourcing-request
cancellation: unfinished renewal sourcing requests now move to cancelled
history instead of being deleted, and audit history uses the same
`sourcing_request.cancelled` event as the Sourcing page.
- Cancelled a pending order automatically when its final license line is
deleted, with a stronger frontend warning and history-preserving audit trail
so empty active purchase orders no longer linger.
- Refreshed procurement-related query state more broadly after sourcing and
pending-order lifecycle changes so portfolio numbers, pipeline counts,
renewal workbench rows, and License Overview renewal statuses update without
waiting for stale cached data.
- Corrected renewal-chain validation so intermediate licenses may retain both
incoming and outgoing links, while administrative repair accepts secondary
coterm predecessors and continues to reject cycles and conflicting
one-to-many successor relationships.
- Preserved established renewal ancestry when cancelling an unfinished
successor renewal from either License Details or Sourcing. Cancellation now
targets only the pending procurement workflow while retaining reciprocal
navigation across the completed predecessor and successor.
- Removed empty original sourcing requests after coterm merge while preserving
requests that still contain unrelated sourcing items.
- Aligned renewal sourcing and pending-order conversion so license type, metric,
pricing, quantity, currency, dates, supplier/contact details, and notes carry
forward consistently. Single and batch conversion now share the same defaults
and backend persistence contract, explicit conversion-time overrides remain
authoritative, and maintenance type, metric, and parent requirements continue
to be enforced.
- Enforced one human-editable target supplier across each sourcing request and
pending order. Renewal history remains unchanged when procurement selects a
different supplier, unresolved renewal bundles and coterm merges remain
unassigned, and resulting licenses use the actual pending-order supplier
instead of stale sourcing or predecessor values.
- Cleared pending renewal state from every coterm predecessor when merged
sourcing or pending-order renewal work is cancelled or deleted.
- Standardized renewal workbench, coterm, and procurement calculations on
Decimal arithmetic. Fractional quantities and configured precision are
retained, invalid numeric values are reported or rejected instead of silently
becoming zero, sourcing merge quantities remain exact and locale-aware
throughout the frontend workflow, and existing API response formatting
remains compatible.
- Kept normal and consolidated renewal ancestry visible while a successor has a
subsequent renewal in progress, alongside the current sourcing or
pending-order progress actions.
- Prioritized outgoing renewal sourcing and pending-order progress in the
Renewal Workbench when a successor license also has incoming ancestry.
- Removed the misleading Procurement Trail evidence counter and aligned its
sourcing request, sourcing line, and pending-order links with the existing
neutral renewal-lineage card presentation. Procurement documents and evidence
transfer behavior are unchanged.
- Prevented modal focus management from overriding an already-focused form
control, avoiding dropped input during rapid custom-field editing.
- Moved scheduled and manually triggered routine backup creation, integrity
checks, and retention pruning off the async API event loop while preserving
serialized execution and existing backup behavior.
- Hardened server-side restore selection so request filenames are used only to
select an inspected archive returned by trusted directory enumeration, rather
than to construct a filesystem path.
- Preserved document metadata after database-only restores while marking
missing or unavailable managed files in document lists, counters, restore
warnings, and native doctor output.
## [1.1.1] - 2026-07-24
### Added
- Added an optional license type to sourcing lines. Zero-cost
freeware/open-source lines can convert directly from sourcing to an active
Registry license without a pending order, purchase date, PO, invoice,
contract, or purchase price. Mixed requests now complete in one action: free
lines enter the Registry while paid lines enter the pending-order path.
- Added maintenance/support classification to sourcing, pending-order
conversion, and direct license entry for perpetual, OEM, and
freeware/open-source records. Included support stores its coverage dates and
either a flat coverage fee or a covered quantity, unit price, and calculated
coverage-period total on the parent. Separately tracked support creates an
editable linked maintenance line and preserves that relationship through
conversion. Included support contributes to sourcing estimates and
pending-order totals exactly once.
- Freeware/open-source records now treat contract, PO, invoice, quote, and
non-expiring end-date requirements as not applicable when no paid support is
included. EULA, proof-of-entitlement, and publisher-contact requirements
remain inapplicable; paid included support restores the purchase-evidence
requirements while preserving department and budget-owner checks.
- Added an admin-only portfolio reset for clean pre-production starts. It
removes current and historical licenses, sourcing requests, pending orders,
contracts, documents, processing results, delivery history, and prior audit
events while preserving users and application configuration. A verified
database-and-document recovery archive is required before deletion, and the
next generated license reference restarts at `LT-REF-00001`.
- Added server-side archive selection to Database Restore while retaining local
file upload. Routine database backups leave managed documents unchanged;
portfolio-recovery and pre-restore safety archives restore both the database
and managed documents after creating a new database-and-document safety
archive. Server selections are restricted to validated archives in the
configured backup directory.
### Changed
- Documented LicenseTrack's milestone and release-train versioning policy so
release numbers match the established practice of shipping backward-compatible
features, fixes, hardening, and documentation within an active `1.x` series.
- Freeware/open-source forms now hide license acquisition pricing and persist
a zero acquisition cost. Perpetual and OEM acquisition pricing remains
separate from optional included-support pricing.
- Reports now exclude zero-cost freeware/open-source records from monetary
totals. Current paid included support contributes its coverage-period total
to recurring cost and forecast calculations, while separately tracked
support continues to report through its maintenance license line.
- Demo frontend builds now use `frontend/dist-demo` instead of replacing the
normal production bundle under `frontend/dist`.
- Native installation now records and reports explicit local-only,
reverse-proxy, or direct-network reachability. Non-interactive installs must
confirm reverse-proxy intent when a non-local public URL uses the secure
loopback bind.
- Validated native installation, upgrade, portfolio-recovery restore, and
automatic post-restore restart behavior on Debian 13 (Trixie).
- Documented the native runtime privilege boundary and a guarded removal
procedure with explicit data-retention, final-backup, service-account, and
external host-cleanup steps.
- Settings and CSV Import now share the same pattern-based number-format
choices, removing duplicate and misleading country labels. Import defaults
to the user's number format but remains overridable per file when source data
uses a different convention.
- Refocused the repository README on the product and the shortest installation
paths; configuration, persistence, hardening, native host, and maintainer
detail now point to their dedicated documentation.
### Fixed
- Native release assembly and installation now reject frontend bundles that
contain the demo-only marker, preventing a stale or contaminated demo build
from being installed as the production application.
- Native release assembly now uses an explicit backend allow-list, preventing
local environment files, databases, backups, uploaded documents, coverage
data, and development plugin storage from entering release archives.
- The SPA shell now requires browser revalidation while fingerprinted frontend
assets use explicit long-lived immutable caching, preventing stale HTML from
continuing to reference an obsolete application build.
- `licensetrack doctor` now reports bind address, public URL, and effective
reachability, warns about unconfirmed legacy reverse-proxy arrangements, and
detects recorded network-mode mismatches.
- Added a native permission-contract verifier for release validation, covering
service access to mutable data and denial of writes to application code,
configuration, systemd, operator tooling, and upgrade snapshots.
- Suppressed Official Extension action and suggestion requests when the host is
unavailable, preventing expected disabled-host responses from appearing as
console errors in Sourcing, Pending Orders, Add License, and License Details.
- Kept the License Overview table header and column filters available when the
active filters return no records, so users can broaden or clear the filter.
- Preserved sourcing-line start and end dates when editing the line from
Pending Orders instead of dropping both fields from the update request.
- Interpreted offsetless SQLite API timestamps as UTC before applying the
user's configured time zone, fixing history times that appeared one or two
hours behind the server's local time.
- Ensured native Linux installations restart automatically after a successful
database restore. The systemd unit now restarts after clean application exits,
and native upgrades refresh the installed unit so the policy reaches existing
deployments.
- Simplified the direct freeware/open-source conversion confirmation without
repeating the pending-order rules already expressed by the workflow.
- Updated the transitive DOMPurify dependency used by PDF export to the patched
`3.4.12` release.
## [1.1.0] - 2026-07-22
### Added
- Added native CPython 3.13 and 3.14 support alongside Python 3.12, with
automatic runtime and ABI selection.
- Added per-ABI offline dependency wheelhouses for `cp312`, `cp313`, and
`cp314` to the combined native Linux release.
- Added native runtime metadata to installation state and runtime compatibility
checks to `licensetrack doctor`.
- Added operator-initiated native rollback through
`sudo licensetrack rollback`, including explicit backup selection,
non-interactive confirmation, configurable health timeouts, and
maintenance-mode operation.
- Added pre-rollback safety backups and automatic recovery of the original
installation if a manual rollback fails after restoration begins.
### Changed
- Replaced the public generic Plugin Host positioning with a trusted
first-party Official Extensions model. The host is disabled by default,
stable mode requires Ed25519 signatures from pinned LicenseTrack release
keys, and existing unsigned installations migrate to a disabled and
unverified state. Custom and third-party automation should use the public
API, webhook, and sidecar integration framework.
- Updated the Official Extensions admin experience to show signer, checksum,
trust state, declared access, and trusted server-code warnings.
- Upgraded the frontend runtime from React 18.3.1 to React and ReactDOM 19.2.8,
with aligned React 19 type definitions while preserving the existing product
behavior and visual design.
- Native release publication is now gated by installation smoke tests across
Python 3.12, 3.13, and 3.14.
- Native upgrades now refresh the installed `licensetrack` operator command
atomically after the upgraded application passes its health check.
- Native upgrade and rollback backups now preserve the matching operator
command so application and management tooling remain aligned after
restoration.
- Updated native deployment and maintainer documentation for the expanded
Python runtime matrix and operator-initiated rollback.
- Updated audited frontend transitive dependencies while retaining npm 10
lockfile compatibility.
### Fixed
- Fixed native installations performed under restrictive umask settings
creating release paths that the system service account could not access.
- Fixed the frontend package lock after dependency auditing so it remains
compatible with the npm 10 environment used by CI.
- Stabilized the demo Playwright renewal-conversion workflow against retained
procurement-history behavior.
- Prevented the Official Extensions settings surface from showing a transient
empty state before host discovery and package loading complete.
### Security
- Restricted Official Extension runtime environment inheritance, enforced
settings and document access declarations for runtime and draft contexts,
and terminated managed extension process trees during disable and uninstall.
- Updated frontend transitive dependencies to resolve reported high-severity
npm audit findings.
### Release
- Version bumped to 1.1.0 across backend, frontend, README, Docker Compose,
frontend package metadata, and wiki installation/deployment examples.
## [1.0.9] - 2026-07-19
### Added
- Added native Linux release archives for systemd-based Ubuntu 22.04 LTS
x86_64 hosts, including checksum verification, a compiled frontend, an
offline Python wheelhouse, and Standard and Advanced installation modes.
- Added native Linux upgrades with pre-upgrade backups, database migrations,
health verification, atomic release activation, automatic rollback, and an
installed operator CLI.
- Added a tag-driven native packaging workflow that publishes `.tar.gz`,
`.zip`, and `SHA256SUMS` release assets.
- Added optional LT-Ref replacement behavior to Native CSV Import, matching the
existing External Tool Import workflow. Exported licenses can now be edited
and re-imported without creating duplicates.
- Added automatic Native Import matching for existing custom fields using their
stable `cf_*` key or an unambiguous display name.
- Added existing custom fields to the External Import mapping selector.
- Added searchable history sections for converted and cancelled sourcing
requests and pending orders.
- Added click-through procurement trail links from license details to
historical sourcing records, pending orders, and created licenses.
- Added license detail history context for sourcing request/item data,
pending-order data, procurement evidence counts, and related navigation.
- Added support for bundled renewal initiation: licenses sharing the same PO
number and end date can create one sourcing request with multiple renewal
lines.
### Changed
- Full Data CSV exports now use stable custom-field keys as headers for reliable
export/edit/re-import round-tripping, including after a field is renamed.
- Native and External imports now share typed custom-field validation and
nonblank update behavior. Blank cells preserve existing values.
- Invalid enum, date, numeric, and typed custom-field values are consistently
treated as row errors instead of acknowledgement warnings.
- Import mapping presets are shared configuration: editors can load and use
presets, while only admins can create, replace, rename, or delete them.
- Custom-field definition creation during import is shown only to admins.
- Converted and cancelled sourcing and pending-order records are now retained
as read-only reference history instead of cluttering active workflow tables.
- Sourcing history can link forward to related pending orders, including
converted orders in pending-order history.
- Pending-order history can link forward to created licenses, with line-level
license links for multi-line orders.
- Tightened coterm renewal merge eligibility to require matching publisher,
software description, license metric, predecessor end date, and compatible
SKU when SKU values are present.
- Updated procurement, registry, integration, API stability, wiki, maintainer,
Help Center, and glossary documentation for sourcing history, pending-order
history, and procurement trail behavior.
- Made license table sortable headers keyboard-activatable and exposed
`aria-sort`.
- Split native runtime dependencies from development and test dependencies so
release archives and container images install only production requirements.
### Fixed
- Fixed Native Import creating duplicate licenses when re-importing records
with matching LT references instead of offering the same replacement
behavior as External Import.
- Fixed Native Import silently dropping recognized custom-field values during
preview, creation, and LT-Ref updates.
- Fixed import confirmation trusting stale preview warnings by rebuilding
duplicate detection, maintenance inference, update targets, and warning
summaries immediately before writing.
- Isolated each imported row in a database savepoint so one persistence failure
no longer invalidates successful rows in the same batch.
- Fixed update-only CSV imports not producing an audit event or reporting their
updated count.
- Rejected non-empty CSV uploads that do not contain a usable header row.
- Rejected maintenance rows whose inferred parent appears later in the import
file, since the parent must be persisted first.
- Normalized imported perpetual licenses so they cannot retain an end date.
- Fixed import mapping renames replacing or losing the mapping payload.
- Prevented non-admin mapped-import requests from saving or overwriting shared
mapping presets through the execute endpoint.
- Clarified completion summaries so manually skipped rows are not incorrectly
described as validation errors.
- Fixed renewal initiation for same-PO/same-end-date license groups creating
separate sourcing requests instead of one multi-line sourcing request.
- Fixed misleading historical sourcing links that pointed only to active
pending orders when the related PO had already converted.
- Fixed stale audit test coverage for coterm merge validation.
- Stabilized the frontend Playwright smoke test for license table publisher
sorting.
### Release
- Version bumped to 1.0.9 across backend, frontend, README, Docker Compose,
frontend package metadata, and wiki installation/deployment examples.
## [1.0.8] - 2026-07-17
### Added
- Added a License Overview toolbar column-category selector so users can quickly
show or hide Standard, Advanced, Computed, and Custom Field list columns while
building saved views, without visiting My Settings.
### Fixed
- Fixed an app-breaking shared frontend query-cache shape mismatch between the
License Overview and Sourcing pages that could crash Sourcing with
`licenses.find is not a function` after navigating from renewal workflows.
- Fixed the related License Overview failure mode where a stale shared cache
could clear the license list and show a negative tracked-license count until
another navigation refreshed the page.
- Hardened shared license-cache readers and mutation helpers so legacy
array-shaped cache data is handled consistently across License Overview,
Sourcing, Pending Orders, and renewal workflow actions.
- Added an explicit SMTP encryption mode fix so mail configuration no longer
relies on ambiguous implicit encryption behavior.
- Hardened OIDC discovery and callback handling with explicit unsafe dev/test
flags for HTTP or private IdP URLs, validation for server-fetched OIDC
endpoints, and safe stage-aware callback diagnostics, including a generic
`callback_failed` stage for unexpected server-side callback errors.
### Release
- Version bumped to 1.0.8 across backend, frontend, README, Docker Compose,
frontend package metadata, and wiki installation/deployment examples.
## [1.0.7] - 2026-07-15
### Security
- Resolved remaining GitHub CodeQL findings by tightening document storage path
validation around an explicit validated-path boundary and moving API token
keyed digests to the cryptography HMAC API.
### Release
- Version bumped to 1.0.7 across backend, frontend, README, Docker Compose,
frontend package metadata, and wiki installation/deployment examples.
## [1.0.6] - 2026-07-15
### Added
- Added `RESTART_AFTER_RESTORE` so Docker/process-manager deployments can keep
the restore-and-exit restart flow while direct local development keeps the
API process running after a database restore.
- Added a local stylesheet ownership map under `frontend/src/styles/README.md`
and updated the style contract so future CSS has a clear destination.
- Added per-user default saved views on the License Overview while keeping the
built-in Default view as a safe reset.
### Changed
- Split the former monolithic frontend stylesheet into ordered CSS partials
while preserving the original cascade through `global.css` as the import
manifest.
- Split PDF export dependencies into separate on-demand chunks so production
builds no longer emit the Vite large-chunk warning.
- Consolidated settings presentation styles across API tokens, import mappings,
custom fields, password and restore controls, global settings, visible
categories, SMTP/email templates, extensions, webhooks, audit log, and
remaining settings stragglers.
- Normalized root Vite/Vitest config formatting and Alembic environment comments
to match the repository style contract.
### Fixed
- Returned restore restart status to the frontend and clarified restore
messaging so local and process-manager restart modes report accurately.
- Covered both restore restart modes with backup integration tests.
### Security
- Hardened CodeQL-reported redirect, storage path, API token digest, email
validation, and test assertion patterns before the 1.0.6 release tag.
### Release
- Version bumped to 1.0.6 across backend, frontend, README, Docker Compose,
frontend package metadata, and wiki installation/deployment examples.
## [1.0.5] - 2026-07-14
### Added
- Added an upcoming license status for records whose entitlement start date is
still in the future, with overview filtering, report counts, notification
handling, and regression coverage.
### Changed
- Added a public style contract and contribution guidance for consistent
AI-assisted backend, frontend, CSS, testing, and release work.
- Normalized backend formatting and comments to the repository style contract.
- Updated form placeholders to use more neutral wording.
- Consolidated frontend modal, settings, license-toolbar, and shared UI
presentation patterns to reduce inline style drift.
- Consolidated global settings response normalization through the canonical
frontend normalizer.
### Fixed
- Reduced frontend hook dependency suppressions to the remaining documented,
license-scoped effects.
- Replaced silent cleanup failures with warning logs where broad cleanup paths
intentionally continue after failure.
- Removed production-source comment artifacts, mojibake, and demo-build marker
leakage from normal production builds.
### Release
- Version bumped to 1.0.5 across backend, frontend, README, Docker Compose,
frontend package metadata, and wiki installation/deployment examples.
- Verified backend tests, frontend lint, frontend tests, frontend production
build, demo marker absence, and tracked-file release-surface scans.
## [1.0.4] - 2026-07-12
### Added
- Added support for multiple invoice numbers per license while preserving
compatibility with the existing single invoice number field.
- Added per-license renewal notification controls so individual records can be
excluded from renewal reminders without changing global notification rules.
### Fixed
- Fixed renewal cancellation warnings by aligning the frontend with the
backend `poWarning` response shape, including demo and regression coverage.
- Fixed stale completeness and notification state after admin mandatory-field
changes by refreshing affected notification, license, report, and stats
queries.
- Included procurement documents in completeness calculations, notifications,
reports, and conversion responses so post-conversion evidence is counted
consistently.
- Locked viewer access out of sourcing and pending-order procurement views
while preserving department-scoped renewal workbench visibility.
- Hardened lifecycle deletion rules for licenses linked to sourcing,
pending-order, procurement-document, renewal, and maintenance relationships.
- Fixed plugin draft suggestion and document-processing response schemas so
draft sourcing and pending-order suggestions can be listed without validation
errors.
- Normalized localized number fields in additional sourcing request lines
before submit.
- Hardened frontend release workflows around API request casing, cache
invalidation, role visibility, mutation side effects, procurement modal
failures, renewal initiation, custom-field admin rollback, unsaved SMTP
settings, and initial settings normalization.
### Security
- Tightened plugin action API-token scope checks for procurement and
document-processing targets.
- Rejected oversized quote and document uploads earlier in request handling.
### Changed
- Updated frontend tooling and GitHub Actions dependencies while deferring the
React 19 upgrade.
- Replaced deprecated Authlib JOSE imports.
### Release
- Version bumped to 1.0.4 across backend, frontend, README, Docker Compose, and
frontend package metadata.
## [1.0.3] - 2026-07-08
### Security
- Upgraded `aiosmtplib` from 3.0.2 to 5.1.2 to address SMTP command
injection via CR/LF in caller-supplied sender or recipient addresses
(GHSA-v3q9-hj7j-63hq).
- Added defense-in-depth email address hardening for notification and settings
flows: budget owner and manager email fields now reject CR/LF/NUL while
preserving existing loose email formatting and CSV round-trip behavior.
- CSV import now flags CR/LF in `budget_owner_email` as a per-row validation
error, and `email_service.send_email()` rejects CR/LF/NUL in `to` and `cc`
before calling the SMTP sink.
- Added release-hygiene dependency audits to CI: `pip-audit` for backend
Python dependencies and `npm audit --audit-level=high` for frontend npm
packages.
- Added Dependabot configuration for grouped weekly backend pip, frontend npm,
and GitHub Actions update PRs.
### Changed
- License exports now label `Quantity` as `Purchase Quantity` and the former
`Total PO Price` as computed `Total PO Value`. The exported PO value is
derived as the sum of `quantity x unit_price` across exported licenses that
share a PO number, instead of reading the legacy stored `total_po_price`
field.
- CSV import recognizes the new `Purchase Quantity` export header and ignores
the derived `Total PO Value` header so fresh exports do not overwrite
per-license stored totals on re-import. Legacy `Total PO Price` imports still
map to the stored field for older CSVs.
- License details, edit forms, invoice confirmation, sourcing, pending-order
conversion, CSV mapping, and help copy now consistently use `Purchase
Quantity` for the bought quantity.
- `Total PO Value` is shown as a computed read-only value in license details
rather than an editable stored field.
- Sourcing and pending-order CSV exports now use the `Purchase Quantity` label.
### Fixed
- Maintenance/support mirror cost now uses the active maintenance child's own
line total (`quantity x unit_price`) instead of the legacy stored
`total_po_price` aggregate, preventing a whole PO value from being attributed
to one maintenance line.
- Renewal sourcing items now seed their estimated total from the renewing
license's line total (`quantity x unit_price`) instead of the stored
`total_po_price` aggregate.
### Maintenance
- Bumped backend dependencies including FastAPI, Uvicorn, Authlib, SQLAlchemy,
Alembic, Pydantic, APScheduler, aiosmtplib, pytest, pytest-asyncio, respx,
ruff, and cryptography.
- Bumped frontend dependencies including React Query, React Virtual, React Hook
Form, Recharts, Playwright, Vitest, and related test/lint packages.
### Release
- Version bumped to 1.0.3 across backend, frontend, README, Docker Compose, and
wiki installation/deployment examples.
## [1.0.2] - 2026-07-05
### Added
- CSV import now supports procurement dates: `request_date` and `purchase_date`
are importable and export with importable headers, so a full export
round-trips cleanly back into the tool.
- CSV import can update existing licenses by LT Ref (mapped flow). Re-importing
an exported list reconciles onto existing records by LT Ref chain-head match
instead of creating duplicates, with an auto-armed toggle and preview counts.
`license_type`, `license_ref`, lifecycle, and maintenance-mirror fields are
immutable on update; ambiguous refs surface a per-row error. The legacy
`/confirm` path stays create-only.
- Admin-created users inherit the creating admin's regional and display
preferences (currency, number/date/time format, timezone, theme, UI size).
Personal layout state (saved views, column order, visible columns) still
starts at defaults.
- User documentation wiki (`wiki/`, MkDocs Material) covering getting started,
importing and understanding licenses, the renewal lifecycle, navigating the
dashboard, and operations. Published to GitHub Pages via a new `Docs`
workflow (`.github/workflows/docs.yml`, `mkdocs build --strict`).
### Changed
- Renamed the Renewal Workbench action from "Start Renewal" to "Initiate
Renewal" so it matches the License Details panel.
- Consolidated operations/deployment reference into the wiki as the single
source of truth: moved `docs/DEPLOY.md`, `docs/operations-runbook.md`, and
`docs/user-guide/Backup and Restore.txt` into `wiki/operations/`. The former
deployment guide is reframed as advanced "Production deployment & hardening";
the beginner quick-start it duplicated now lives in the Installation guide.
- Repointed `README.md` and `docs/plugin-host-v1-roadmap.md` doc links to the
new wiki paths.
### Fixed
- Sourcing item start/end dates are now preserved through conversion to a
pending order and pre-filled into the convert-to-license form.
`SourcingItemSummary` (nested in the pending-order response) previously
omitted the dates, and the convert form hardcoded them blank.
- The first-launch login page now renders in light mode instead of gray,
matching the post-login default for a consistent first impression.
- CSV import round-trip: `_IGNORED_HEADERS` lets a full export re-import skip
computed/metadata columns instead of prompting custom-field creation, and the
Flexera `purchase_date` alias now maps to the real field instead of
`start_date`.
### Removed
- `docs/DEPLOY.md`, `docs/operations-runbook.md`, and
`docs/user-guide/Backup and Restore.txt` (relocated to `wiki/operations/`).
## [1.0.1] - 2026-07-04
### Security
- Exclude nested `.env` files (e.g. `backend/.env`) from the Docker build context
so local secrets can no longer be baked into a built image.
- Docker Compose no longer supplies a default `ADMIN_PASSWORD`; an unset value now
fails startup instead of silently using a placeholder. Added `changeme_required`
to the weak-password blocklist as defense-in-depth.
- Bump `pydantic-settings` to 2.14.2 (resolves a symlink-escape advisory in
`NestedSecretsSettingsSource`) and `dompurify` to 3.4.11.
- Docker Compose now runs the container with `no-new-privileges`.
### Changed
- Frontend build stage now uses `node:22-alpine` (Node 20 reached end-of-life).
### Docs
- Removed the internal Plugin Host v1 implementation-plan and Pydantic schema-plan
documents (build scaffolding for already-shipped functionality) and corrected
stale version references in the remaining plugin-host docs.
- Added Podman deployment instructions and a production-hardening section (HTTPS,
`SESSION_COOKIE_SECURE`, reverse-proxy, and trusted-network guidance) to
`docs/DEPLOY.md`.
## [1.0.0] - 2026-06-16
First public source-available release. LicenseTrack is a self-hosted software
license procurement and lifecycle management system, deployed via Docker.
### Security (post-release hardening)
Follow-up hardening from an internal security review. No API contract changes;
the release remains 1.0.0.
- Login throttling now counts failed attempts by source IP in addition to
username, so a password spray across many usernames is throttled and can no
longer bypass the per-username limit. The failed-attempt counters are also
capped so a flood of unique usernames or IPs cannot exhaust memory.
- Login now performs a constant-time dummy password verification when the
username does not exist, removing a timing side channel that could be used to
enumerate valid usernames.
- The interactive API docs (`/docs`, `/redoc`) and OpenAPI schema
(`/openapi.json`) are disabled by default and gated behind the new
`EXPOSE_API_DOCS` setting, so the full API surface is no longer published to
unauthenticated callers unless explicitly enabled.
### Procurement
- Sourcing requests for evaluating license purchases, capturing publisher,
supplier, contact, quantity, estimated cost, status, renewal context, and
quote documents.
- Promotion of sourcing items into pending purchase orders, with grouping of
multiple items under a single purchase order.
- Conversion of pending orders into live license records.
- Renewal opportunity detection with cotermed renewal workflows.
### License registry
- Searchable, filterable license records covering publisher, contract, purchase
order, dates, quantities, costs, status, custom fields, and notes.
- Status filters for active, expiring, expired, pending renewal, renewed,
retired, legacy, complete, and incomplete records.
- Configurable visible columns, column reordering, saved display preferences,
and CSV export.
- Create, edit, retire, renew, and link license records, with record history
(creator account, creation timestamp, latest update timestamp) in the detail
panel.
- Preservation of sourcing-request and purchase-order milestone dates on the
resulting license records, with manual enrichment for imported and legacy
data.
### Renewals
- Dedicated renewal workbench for upcoming, overdue, and in-progress renewals.
- Renewals started from existing license records, carrying data through the
sourcing, pending order, and conversion workflows.
- Renewal-chain traceability with preservation of historical license records.
- Renewal consolidation for coterm opportunities.
### Contracts and documents
- Contract records grouped by publisher and contract number, linkable to
license records.
- Contract-level document storage in user-defined folders.
- File attachments (invoices, EULAs, proofs of entitlement, quotes, and others)
for licenses, contracts, sourcing requests, and procurement records.
- Completeness tracking based on mandatory fields and required document
presence.
### Reporting and analytics
- Read-only analytics workspace covering the full license portfolio, with a
global filter bar: include/exclude retired records, start-date range (all
time, this year, last 12 months, or a custom range), and multi-select
cost-centre (department) filtering.
- Portfolio summary stats: active, expiring, and expired counts, plus total
annual portfolio cost reported per currency, with an indicator for records
excluded from totals.
- Cost overview and budget forecast: total historical spend (deduplicated by
purchase order), recurring annual cost, non-recurring spend, and lifecycle
budget split across active, expiring, and expired records; multi-year budget
projection with configurable forecast horizon and annual growth percentage.
- Spend by publisher, and a publisher/supplier relationship table with license
counts, per-currency spend, and unpriced-record flags.
- Portfolio breakdown by license type and license metric.
- Fiscal-year-aware renewal calendar projecting renewal counts and estimated
value across the next four quarters, honoring the configured fiscal-year
start month.
- Per-currency reporting throughout, with no implicit currency conversion and a
clear disclaimer when a filtered set mixes currencies.
- Export of the full report to PDF.
- CSV import and CSV export for operational reporting.
### Administration
- Configurable mandatory fields, legacy handling, and completeness exemptions.
- In-app notifications and optional SMTP email alerts.
- Manual and scheduled database backups, with a pre-restore safety snapshot on
restore.
- User management with Admin, Editor, and Viewer roles.
- Optional OIDC/SSO with a protected local break-glass admin account.
- Audit history for authentication, settings, user, database backup, document,
and data-changing actions, with configurable retention.
### Integration and extensibility
- Documented, unversioned API with token authentication, application-version
compatibility, and defined stability levels (see
[docs/extension-authors/api-auth.md](docs/extension-authors/api-auth.md) and
[docs/extension-authors/api-stability.md](docs/extension-authors/api-stability.md)).
- Optional extension points: document actions, declared capabilities,
document-processing results, and webhooks.
- Author guides and copyable recipes for integrations and document processors
(see [docs/extension-authors/build-integrations.md](docs/extension-authors/build-integrations.md),
[docs/extension-authors/build-document-processor.md](docs/extension-authors/build-document-processor.md), and
[docs/extension-authors/integration-recipes.md](docs/extension-authors/integration-recipes.md)).
### Deployment and security
- Docker and Docker Compose deployment serving the compiled frontend from the
backend container via same-origin `/api` URLs.
- JWT sessions, bcrypt password hashing, and encryption of stored integration
secrets derived from `JWT_SECRET`.
- Startup refuses blank or common default values for `JWT_SECRET` and
`ADMIN_PASSWORD`.
- Configurable upload size and extension allow-list, CORS origin allow-list,
and session cookie controls.
[Unreleased]: https://github.com/zndr88/LicenseTrack/compare/v1.2.1...HEAD
[1.2.1]: https://github.com/zndr88/LicenseTrack/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/zndr88/LicenseTrack/compare/v1.1.24...v1.2.0
[1.1.24]: https://github.com/zndr88/LicenseTrack/compare/v1.1.23...v1.1.24
[1.1.23]: https://github.com/zndr88/LicenseTrack/compare/v1.1.22...v1.1.23
[1.1.22]: https://github.com/zndr88/LicenseTrack/compare/v1.1.21...v1.1.22
[1.1.21]: https://github.com/zndr88/LicenseTrack/compare/v1.1.20...v1.1.21
[1.1.20]: https://github.com/zndr88/LicenseTrack/compare/v1.1.19...v1.1.20
[1.1.19]: https://github.com/zndr88/LicenseTrack/compare/v1.1.18...v1.1.19
[1.1.18]: https://github.com/zndr88/LicenseTrack/compare/v1.1.17...v1.1.18
[1.1.17]: https://github.com/zndr88/LicenseTrack/compare/v1.1.16...v1.1.17
[1.1.16]: https://github.com/zndr88/LicenseTrack/compare/v1.1.15...v1.1.16
[1.1.15]: https://github.com/zndr88/LicenseTrack/compare/v1.1.14...v1.1.15
[1.1.14]: https://github.com/zndr88/LicenseTrack/compare/v1.1.13...v1.1.14
[1.1.13]: https://github.com/zndr88/LicenseTrack/compare/v1.1.12...v1.1.13
[1.1.12]: https://github.com/zndr88/LicenseTrack/compare/v1.1.11...v1.1.12
[1.1.11]: https://github.com/zndr88/LicenseTrack/compare/v1.1.10...v1.1.11
[1.1.10]: https://github.com/zndr88/LicenseTrack/compare/v1.1.9...v1.1.10
[1.1.9]: https://github.com/zndr88/LicenseTrack/compare/v1.1.8...v1.1.9
[1.1.8]: https://github.com/zndr88/LicenseTrack/compare/v1.1.7...v1.1.8
[1.1.7]: https://github.com/zndr88/LicenseTrack/compare/v1.1.6...v1.1.7
[1.1.6]: https://github.com/zndr88/LicenseTrack/compare/v1.1.5...v1.1.6
[1.1.5]: https://github.com/zndr88/LicenseTrack/compare/v1.1.4...v1.1.5
[1.1.4]: https://github.com/zndr88/LicenseTrack/compare/v1.1.3...v1.1.4
[1.1.3]: https://github.com/zndr88/LicenseTrack/compare/v1.1.2...v1.1.3
[1.1.2]: https://github.com/zndr88/LicenseTrack/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/zndr88/LicenseTrack/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/zndr88/LicenseTrack/compare/v1.0.9...v1.1.0
[1.0.9]: https://github.com/zndr88/LicenseTrack/compare/v1.0.8...v1.0.9
[1.0.8]: https://github.com/zndr88/LicenseTrack/compare/v1.0.7...v1.0.8
[1.0.7]: https://github.com/zndr88/LicenseTrack/compare/v1.0.6...v1.0.7
[1.0.6]: https://github.com/zndr88/LicenseTrack/compare/v1.0.5...v1.0.6
[1.0.5]: https://github.com/zndr88/LicenseTrack/compare/v1.0.4...v1.0.5
[1.0.4]: https://github.com/zndr88/LicenseTrack/compare/v1.0.3...v1.0.4
[1.0.3]: https://github.com/zndr88/LicenseTrack/compare/v1.0.2...v1.0.3
[1.0.2]: https://github.com/zndr88/LicenseTrack/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/zndr88/LicenseTrack/releases/tag/v1.0.1
[1.0.0]: https://github.com/zndr88/LicenseTrack/releases/tag/v1.0.0