A tag can exist without a release (pushed by hand), and that still means
the version is taken. Checking the tag makes the failure say 'bump the
version' instead of surfacing a raw NuGet duplicate error.
Two branches: dev for everyday work, production for releases.
- ci.yml builds and packs on dev and on pull requests; it never publishes.
- release.yml runs on a push (usually a merge) into production: reads
<Version> from the csproj, refuses to proceed if that tag already
exists, publishes to nuget.org, and only then creates the tag and the
Gitea release — so a tag always means a published package.
- Drop --skip-duplicate: a duplicate push must fail loudly instead of
reporting success while shipping nothing.
Standalone home for the CertifiEd .NET client SDK, split out of the
platform repository with its history.
- src/ layout with its own solution, Directory.Build.props and
global.json (warnings as errors, XML docs required).
- Gitea Actions CI: build and pack on every push, publish to nuget.org
on a v* tag using the NUGET_API_KEY secret.
- CONTRIBUTING documents the release flow and the one-time CI setup.
- Hardware binding: License.HwBinding (none | fixed | firstActivation).
firstActivation leaves the license unbound until the first activation,
which claims the machine; afterwards it behaves like fixed. Legacy rows
backfilled to fixed when a fingerprint was already set.
- Rebind: POST /panel/licenses/{id}/rebind moves a license to new
hardware (or releases the binding), deactivating existing activations
and re-signing a new version; 409 on revoked/expired. Operator or
company admin. Audited, emits license.rebound.
- Operator management: list, invite, promote and demote operators
(operator-only), guarding self-demotion and the last operator — until
now a second operator could only be seeded directly into the database.
- SDK packaging: CertifiEd.Client is now a proper NuGet package (id,
version, MIT license, README, XML docs, symbols) and gained the
missing public XML documentation.