Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

All notable changes to Depsilo will be documented here.
Format based on [Keep a Changelog](https://keepachangelog.com/).

## [Unreleased]

## [0.13.0] - 2026-11-11

### Added
- Free-edition export allowance for signed transfer bundles
  ([ADR-0009](docs/adr/0009-usage-limited-free-edition.md), schema v16):
  without Pro or an active trial, each bundle may be at most 500 MiB and the
  bundles exported in one UTC month at most 5 GiB. A bundle beyond either limit
  is refused with `402 EXPORT_BUNDLE_LIMIT` or `402 EXPORT_MONTHLY_LIMIT` before
  any byte streams. Only completely streamed bundles count, exports under Pro
  or a trial are not metered, and imports are never limited.
  `GET /api/v1/admin/distribution/usage` and `depsilo distribution usage`
  report the current allowance.

### Changed
- Project SBOM export, including CRA mode, the technical-file preset, and
  signing, is part of the free edition for its one project. The Admin Projects
  page no longer shows a Pro badge or paywall for it, and the License page lists
  the free edition's export allowance.

### Fixed
- A PyPI artifact missing on the selected Upstream is fetched from the other
  Upstreams of the ecosystem before a 404 reaches the client. A mirror such as
  TUNA publishes no PEP 658 core metadata and lags new releases, so after a
  failover the package index cached from pypi.org could advertise files the
  mirror then answered with 404, failing pip and uv installs. A 404 does not
  mark an Upstream unhealthy, and signed external artifact references stay
  bound to their source.
- A request the selected Upstream cannot serve, because the connection fails
  or it answers 5xx or 429, moves on to the other Upstreams of the ecosystem
  instead of failing until health checks notice the outage. This covers PyPI
  artifacts and the APT, Alpine, Conda, CRAN, Go, Helm, Maven and RubyGems
  proxies. APT, Alpine, Conda and Go packages that a mirror answers with 404
  are fetched from another Upstream too, since their clients verify packages
  against signed or checksum-bound metadata; index files never move on a 404.
  Those proxies now answer 404 rather than 502 when every Upstream reports a
  file missing.
- An Upstream whose requests fail slowly twice in a row, each waiting at least
  10 seconds as on a link that drops packets, leaves selection. An active
  Upstream is re-probed after 30 seconds and then at doubling delays of up to 5
  minutes; a passive one receives one half-open request per minute, even while
  a lower-priority Upstream serves. Previously only the next regular probe, 30
  minutes later by default, or a collapse of the lifetime success rate removed
  it, and a passive Upstream had no such probe, so every request kept waiting
  for the connection timeout.
- An expired package index whose refresh is still waiting after 10 seconds is
  served from cache while the refresh continues, instead of holding the
  request for up to the 60-second response timeout. pip gives up after 15
  seconds by default, so it no longer times out and retries while an Upstream
  is not answering. Prefetch and operator-forced refreshes still wait for the
  refresh itself.
- While the known-malicious dataset covers PyPI, a legacy archive (such as a
  `.zip` sdist) proves its identity from the cached project index. After a
  restart without network such archives were refused with `451` even when
  cached, because identity checks always asked the upstream. A candidate
  project whose index does not exist (404) now counts as not declaring the
  archive instead of failing the whole identity check.
- A Docker manifest `HEAD` is answered from the cached manifest when the
  registry or its token service cannot answer. Clients that resolve tags with
  `HEAD`, such as containerd, received `502` even though the manifest and its
  layers were cached, so cached images could not be pulled offline. With the
  observation-age gate enabled, tags still require the registry.
- Recording dependency usage retries when another write commits between its
  read and its write. SQLite rejected such a write at once with
  `database is locked`, which could also make a clean shutdown report an error.

### Security
- Release builds use Go 1.27.4, which fixes
  [GO-2026-6859](https://pkg.go.dev/vuln/GO-2026-6859): a header race in
  `net/http`'s `MaxBytesReader` could crash an HTTP/1 server.

### Upgrade notes
- Schema migration v16 runs automatically on first start and is forward-only:
  an older binary cannot open the upgraded database. Take a backup first
  (`depsilo backup`), and keep `data/secrets/jwt.key` with the database if the
  instance generated one.
- Without Pro or an active trial, transfer-bundle exports are now metered: at
  most 500 MiB per bundle and 5 GiB per UTC month. Exports made before the
  upgrade were not recorded and do not count.
- The APT, Alpine, Conda, CRAN, Go, Helm, Maven and RubyGems proxies answer a
  file that every Upstream reports missing with `404` instead of `502`.

## [0.12.2] - 2026-11-10

The `v0.12.0` and `v0.12.1` tags were not published. Release qualification of
`v0.12.0` failed on a Docker mirror check that read request counters before the
batched access log flushed them; the corrected check verifies that two clean
Docker clients pull through Depsilo and that the second is served from its
cache. Qualification of `v0.12.1` then caught the PyPI core-metadata defect
fixed below. 0.12.2 contains the same changes and both corrections.

### Added
- `[egress]` declares upstream network egress (restart-managed):
  `upstream_proxy` is the default HTTP(S) proxy for package Upstreams and extra
  indexes without their own proxy or cascade parent, `no_proxy` keeps listed
  Upstream hosts direct, and `private_ranges` declares parts of
  `100.64.0.0/10` (Tailscale, Headscale, NetBird) private networks so such
  Upstreams and cascade parents are reachable. Declared ranges are treated as
  private, so public Upstreams still cannot redirect into them;
  `100.100.100.200` stays blocked, and `198.18.0.0/15` is rejected in favor of
  `upstream_proxy`. Docker registries keep their own proxy and DNS checks.
- Dependency-set preparation catalog (opt-in `[distribution]`, schema v13):
  authenticated CLI/API staging of PyPI wheels and metadata for a declared
  Linux/amd64 Python target into retained storage that is separate from the
  package and compiler caches, with independent capacity accounting and
  streaming length/SHA-256 verification (`depsilo distribution stage`, `list`,
  `inspect`, `verify`, `delete`, `prune`). Sets are not served to package
  managers and have no Admin UI yet. See [docs/distribution.md](docs/distribution.md).
- Signed full transfer bundles (schema v14): `depsilo distribution export`,
  `import` and `keygen`, backed by `GET /api/v1/admin/distribution/sets/:id/bundle`
  and `POST /api/v1/admin/distribution/import`. A bundle is a streaming USTAR
  archive whose manifest carries a detached Ed25519 signature from a dedicated
  transfer key (`distribution.transfer_signing_key_file` on the exporter,
  `distribution.trusted_transfer_keys` on the importer). Import authenticates
  the signer before writing, streams and verifies every object, never activates
  or serves a set, and records `preparer_attested` provenance only after the
  whole bundle verified; repeating an interrupted import reuses verified
  content.
- Multiple named Admin authentication sources (schema v12, ADR-0007): OIDC
  sources with distinct issuers and LDAP over LDAPS or mandatory StartTLS, each
  with isolated connection and sign-in tests, explicit account linking, and
  bounded external sessions. Casdoor and OpenLDAP integration fixtures cover
  the flows.
- The free edition includes one project, with its connection URL, package
  inventory, and project token. Creating more projects and per-project SBOM
  export still require Pro or the trial; existing projects keep working after
  entitlement expires.
- Cascade traffic attribution: direct, cascade-peer and redirect origin traffic
  are metered separately (`depsilo_upstream_response_bytes_total`,
  `depsilo_upstream_exchanges_total`, and their `redirect` counterparts), and
  `scripts/benchmark-cascade.py` measures isolated native-client cache reuse
  across a parent and children.
- Interrupted PyPI and PyTorch artifact downloads resume with bounded
  same-source range retries on strong validators; Admin → Cache shows download
  progress.
- Admin can initialize the Docker Hub mirror, showing saved versus running
  state and client connection guidance.
- Lifetime dependency-usage evidence (schema v11) records completed response
  delivery independently from the lossy request log.
- Failed Upstream checks are explained. The Upstream list and check results
  carry a bounded `last_failure` / `failure` (`egress_fake_ip`,
  `egress_shared_address`, `egress_reserved`, `egress_private`, `dns`,
  `timeout`, `tls`, `connection_refused`, `http_status`, `network`) with the
  refused address and range, visible to every Admin role; Admin shows the
  observed cause with the configuration that resolves it, and whether an
  Upstream uses the default egress proxy (`proxy_source`).
- Cascade cache peering (ADR-0006): a Depsilo node can egress selected
  upstreams through another authenticated Depsilo node
  (`[cascade]` + `[[cascade.peers]]` + per-upstream `via`, schema v10).
  The parent fetches the raw upstream exchange, caches it under the `relay`
  adapter type with the child's own TTL and metadata/artifact class, and
  streams it back, so every protocol keeps its normal rewriting, provenance,
  and policy behavior. Chains carry instance IDs and a hop budget, reject
  loops with `508`, keep private targets behind the parent's configured
  upstream origins, and strip credentials unless a peer opts in. Multi-level
  cache hits arrive at LAN speed; Docker, shared control plane, HA, and
  multi-peer bandwidth aggregation remain out of scope. A read-only
  Admin → Upstreams → Cascade page shows the node role, instance ID, parent
  peers, egress bindings, and dangling peer references, and can now edit the
  `[cascade]` section and peer list in place. The editor writes `config.toml`
  atomically, keeps secrets write-only, validates the full document (including
  upstream `via` references) before saving, requires a restart to apply, and
  disables itself with an inline explanation when the account lacks the write
  role or the file is not writable. The same page generates copy-ready client
  commands (npm/pip/Go), a child-Depsilo peer snippet with a relay probe, and
  an upstream-parent snippet with per-peer health checks; the address field is
  a local-only generator and secrets stay placeholders. The page is organized
  as a live topology diagram, an inline configuration form, a tabbed
  quick-connect card, and the upstream bindings table.

### Changed
- An instance without an explicit JWT secret generates a 256-bit instance key
  at `data/secrets/jwt.key` (mode 0600) and reuses it. The key also signs npm
  and extra PyPI artifact URLs, so keep it with the database; CLI backups
  include the effective key. `depsilo setup token` prints the current first-run
  bootstrap token, which is removed after setup.
- Admin Overview leads with cache benefits, shows activity as overlapping area
  charts with explicit loading, empty and sample states, and verifies a new
  client connection end to end (request detected, artifact delivered, cache
  hit).
- Admin management pages state loading, failure, permission and stale-data
  conditions explicitly, with consistent retry and recovery flows across
  workspaces. Shared controls are consolidated on shadcn `base-nova` and Base
  UI without runtime dependency changes.
- Admin → Cache folds finished download tasks when the page opens; an active or
  failed download opens the list, and finished entries no longer repeat a full
  progress bar.
- Docker image builds accept a `GOPROXY` build argument for restricted networks.

### Fixed
- pip and uv installs failed with `451 QUARANTINED` when a PyPI Upstream
  published PEP 658 core metadata (pypi.org does) while the known-malicious
  dataset covered PyPI, which is the default since 0.11.0: the `.metadata`
  file carried no recognizable package identity. It now shares the identity
  and policy decision of the artifact it describes, so a blocked release is
  refused through its metadata as well.
- Vulnerability severity is scored from OSV's CVSS v3 vectors. The parser read
  the vector as a number, so every OSV-sourced advisory was stored as
  `unknown` with score 0: the critical count stayed 0 and severity suggestions
  stayed empty. Advisories without a v3 vector use GitHub's qualitative rating
  when present, without a numeric score.
- Periodic vulnerability scans no longer replace stored advisory details with
  bare IDs. OSV's batch endpoint lists only IDs and modification times; a
  package's complete records are now fetched whenever its listing names a new,
  changed, or previously misparsed advisory, and a failed fetch keeps the
  stored records. Schema v15 records each advisory's parser revision and makes
  every package due for a fresh scan after upgrading.
- A failed Upstream no longer shows the duration of its failed attempts as its
  average response time in Admin or the Portal monitor.
- The Admin sidebar wraps a development build label such as `dev·1eb9578`
  only after its separator, so the commit ID is never split, and the Portal
  shows the same build label as Admin instead of `v0.11.0+dev`.
- Audit chain appends retry transient SQLite busy and stale-snapshot conflicts.
- Upstream probe intervals are normalized, status refreshes stay synchronized,
  and a binding to a removed cascade peer can be cleared.
- The installer resolves the newest release through the
  `github.com/depsilo/depsilo/releases/latest` redirect instead of the
  unauthenticated GitHub REST API, which allows only 60 requests per hour per
  IP. A shared NAT or office network that exhausted that quota made
  `curl -fsSL https://depsilo.com/install.sh | bash` fail with "Could not
  fetch the latest release metadata"; the API remains the fallback for
  downloaders or networks where the redirect is unavailable.

### Security
- Frontend dependencies flagged by npm audit are updated: follow-redirects
  1.16.3 (GHSA-mrwp-f3q8-3g9p, through axios) and, in the build toolchain,
  zod 4.6.7 (GHSA-mrhx-c7xc-m3w5) and minimatch 11.0.5 for the typescript-eslint
  parser (GHSA-g3hv-75wg-m249).

### Upgrade notes
- Schema migrations v11 to v15 run automatically on first start and are
  forward-only: an older binary cannot open the upgraded database. Take a
  backup first (`depsilo backup`), and keep `data/secrets/jwt.key` with the
  database if the instance generated one.
- After upgrading, every vulnerability check is due. The next scan fetches
  complete OSV records again, at most one package request per second, so
  severity counts and suggestions fill in gradually.
- `[egress]` and `[distribution]` are optional. Without `[egress]`, Upstreams
  keep direct egress and the built-in address rules; dependency-set
  distribution stays disabled until `[distribution]` enables it.

## [0.11.0] - 2026-10-06

### Added
- Docker first-observation age gate: registries expose no portable publish
  time, so an operator can now acknowledge `supply_chain.observation_sources =
  ["docker"]` and run a positive Docker minimum-release-age threshold. An armed
  gate resolves tags to a digest with a registry-token-aware HEAD, records the
  first time this instance saw that digest (schema v9,
  `docker_image_observations`), and fetches the manifest pinned to the digest
  so a tag move cannot serve bytes that were not gated. Unseen digests stay
  quarantined until the threshold passes, a registry that omits
  `Docker-Content-Digest` fails closed, and the capability summary reports the
  age as `observed` — first sighting by this instance, explicitly not a
  publisher release time.
- Audit checkpoints can be pushed directly to a remote anchor
  (`[audit] checkpoint_url` + optional bearer token) instead of relying on an
  operator to ship the local file: the same NDJSON line is POSTed whenever the
  head changes, with per-sink deduplication, a 60-second retry after failures,
  and delivery status (`remote_head_id`, `remote_last_success_at`,
  `remote_last_error`) in the integrity API and the audit page. A stalled
  remote feed is reported as a warning; a checkpoint that contradicts the
  stored row remains a hard failure with its id.
- Audit chain anchoring: an optional `[audit] checkpoint_file` receives
  append-only chain-head checkpoints (NDJSON, fsynced, one line per changed
  head). `depsilo audit verify`, the integrity API, and the audit page
  cross-check every checkpointed `(head_id, head_hash)` pair against the
  stored row, so a rewritten or truncated chain — internally consistent and
  therefore invisible to the chain walk alone — is reported with its row id.
- The audit log is now tamper-evident: every row written since schema v8
  carries `prev_hash`/`hash` (versioned SHA-256 payload, UTC-normalized
  timestamps, unique `prev_hash` so concurrent writers cannot fork the chain).
  The audit page shows the verification result with a re-verify button,
  `GET /api/v1/admin/audit/integrity` returns the full report, and
  `depsilo audit verify [--json]` recomputes the chain offline against the
  SQLite file. Rows written before the chain are reported as a pre-chain
  prefix instead of being presented as covered; editing, deleting, or
  re-ordering a chained row names the first broken id.
- Published the threat model ([docs/threat-model.md](docs/threat-model.md)):
  deployment assumptions, the asset inventory, in-scope adversaries, trust
  boundaries (including the deliberately unauthenticated proxy surface and
  the shared-cache/no-tenant-isolation boundary), threat-by-threat controls
  with residuals, the accepted-risk list, and a verification map that ties
  each control to its tests.
- SIEM audit routing: the durable audit stream can be forwarded to external
  collectors as NDJSON or Splunk HEC envelopes. Each exporter keeps a cursor
  over `audit_logs`, so delivery is at-least-once, a collector outage stops the
  cursor instead of dropping records, restarts resume from the last successful
  batch, and failures back off from 10s to 5min. Event filters select actions
  and outcomes (`blocked`, `miss`, …); new exporters start at the current audit
  head. Admin API and an audit-page section cover CRUD, enable/disable, lag and
  last-error status, and a test delivery through the real path.
- Published the compatibility policy ([docs/compatibility.md](docs/compatibility.md)):
  patch releases stay compatible; before `v1.0.0` a breaking API/config change
  needs a full minor release or 90 days of deprecation (whichever is longer),
  and from `v1.0.0` the window is two minor releases or 180 days. It documents
  the `/api/v1` additive rule, `config_version`/unknown-key behavior, one-way
  schema migrations with backup-as-rollback, and the `/vN` document format
  identifiers, with contract tests enforcing the checkable parts.
- Freeze / golden snapshots: promote the cached artifact set into a named,
  hash-pinned snapshot, export and import the manifest
  (`depsilo/snapshot/v1`), and switch snapshot-only mode on from the Cache
  page or the Admin API. Snapshot mode refuses anything outside the active
  snapshot with 451 `SNAPSHOT_BLOCKED` after the malware gate, and
  membership lookups fail closed. Snapshots pin the SHA-256 values recorded
  by tamper detection; importing a manifest seeds missing baselines so a
  first fetch that differs from the manifest raises a tamper alert.
- The per-project SBOM export (Pro) gains a CRA mode and a technical-file
  preset. CRA mode carries the NTIA/CRA minimum elements: purl, SHA-256
  checksums recorded by tamper detection, supplier/license fields, and
  explicit product-to-component dependency relationships. The preset wraps
  the SBOM in a manifest with product identity, manufacturer, coverage
  counts, relationship depth, and explicit limitations. Supplier and license
  values come from operator-declared `[compliance.components]` annotations;
  unlisted components export as unknown. With a configured Ed25519 key,
  `sign=true` returns a detached signature over the exact response body so
  the document itself stays valid JSON for its format.
- The guaranteed malicious-package dataset now covers PyPI and RubyGems.
  PyPI verifies legacy archive filenames (`.zip`, `.egg`, `.tar.bz2`, legacy
  sdists, …) against the declaring package's simple index; RubyGems resolves
  the exact gem identity, including platform artifacts, from the compact
  index. While the dataset covers an ecosystem, an artifact whose identity
  cannot be proven is refused instead of being served unchecked, and
  RubyGems matching treats `1.17.2-x86_64-linux` as the `1.17.2` release.
- Minimum-release-age enforcement is available for npm, PyPI, Composer, NuGet,
  Cargo, and RubyGems. npm carries the packument `time[version]` inside the
  authenticated tarball token; PyPI negotiates the PEP 691 JSON simple index
  and carries `upload-time` with the exact upstream that declared the artifact;
  Composer uses the p2 metadata entry's own `time` field; NuGet uses the v3
  registration index's `published` timestamp for the same immutable version;
  Cargo uses the sparse index entry's `pubtime` and `cksum`; RubyGems resolves
  the compact index's `created_at` and checksum for the exact `.gem` filename.
  Conda is available with approximate Last-Modified provenance after the
  operator acknowledges it through `supply_chain.approximate_sources`, and the
  capability summary labels it approximate. CRAN joins under the same
  acknowledgement: the current source tarball uses the DESCRIPTION
  `Date/Publication`, and archives or binaries use the artifact's
  Last-Modified. Maven and Alpine also join with approximate Last-Modified
  provenance (the exact artifact is HEADed through the serving upstream, and
  lookups are memoized per upstream and path). Helm joins under the same
  acknowledgement: the chart identity comes from a streamed `index.yaml`
  scan (chart filenames cannot be split safely), the exact chart is HEADed for
  its Last-Modified, and artifacts the index does not declare are refused.
  Missing, legacy, HTML-only, or unlisted metadata fails closed while the gate
  is enabled. Composer remains best-effort because its clients can fall back to
  the original dist URL after a 451, and every other ecosystem still rejects
  positive thresholds at startup.

### Changed
- The Admin Overview got a correctness pass: request-rate and traffic series
  are named by link direction instead of implying a source classification,
  origin traffic that was never collected is separated from a measured zero,
  each trend states its aggregation window, the first load keeps its layout and
  makes no pre-payload claims, and the idle status strip fits its guidance
  without overflow.
- The S3 release contract now runs against pinned RustFS, replacing the
  SeaweedFS image that shipped with v0.10.1.

## [0.10.1] - 2026-10-06

### Added
- The Admin Overview now presents live service status, runtime resources,
  client-facing and origin request rates and traffic, cache benefits, activity
  trends, and recent requests, with centered detail dialogs that preserve deep
  links and page state.
- Overview reports the cached package inventory size, the number of served
  requests, and an estimated time saved derived from comparable cache-hit and
  origin latency samples.
- macOS process memory reports the current RSS through libproc instead of a
  peak-only explanation string.
- Portal Monitor pins upstream counts while scrolling, marks problem upstreams
  in the heartbeat, and reports service availability rather than mirror health.

### Changed
- The minimum toolchain is Go 1.27.1, which is also what CI and the container
  build now use.
- The web interface now uses shadcn/ui primitives over the existing Base UI
  layer. Admin workspace destinations act as page headers, and route chunks are
  warmed on tab switches instead of falling back to a loading state.
- The Depsilo brand kit mark, palette, and typography are applied across Portal
  and Admin, including the restored pale Overview palette and calmer Portal
  accent.
- Overview metric tiles are icon-first with hover measurement hints, and the
  small-screen layout sizes icons from the card instead of the viewport.
- The shadcn Tailwind layer used by the web UI is vendored under
  `web/src/styles/`, so the shadcn CLI is no longer a frontend devDependency.
  This removes its unfixable `braces` advisory chain without changing any
  component behaviour.

### Fixed
- npm metadata for packages whose names differ only in case (legacy uppercase
  names such as `JSONStream`) no longer collide in the local cache on
  case-folding filesystems such as macOS and Windows.
- Rejecting a quoted `min_cvss_score` keeps working on Go 1.27, where
  `encoding/json` runs the v2 implementation by default.
- Frontend dependencies no longer carry the known high-severity advisories
  that blocked CI: `axios` is upgraded to 1.20.0, `brace-expansion` is pinned
  to 1.1.21 / 5.0.12, and `source-map-js` is upgraded to 1.2.2.
- The v0.9.0 Compose upgrade helper now prepares its bind-layout state
  portably, and narrow Admin settings rails scroll instead of overflowing.
- Portal text no longer drops below the readable floor, sticky headers survive
  the body and root containers, and the phone ecosystem picker stays compact.
- The S3 release contract now runs against a pinned SeaweedFS image, because
  MinIO withdrew its public community images and binaries in October 2026. The
  contract still verifies signed streaming, multipart upload, listing,
  deletion, and cross-platform images through an authenticated endpoint.
- The v0.9.0 and v0.9.1 release upgrade contracts now derive the expected
  schema version from the candidate source instead of a hardcoded value, and
  wait for the legacy artifact cache row before shutting the fixture down.
- The v0.9.0 Compose contract verifies the published state directory by inode
  identity instead of reading a host-inaccessible `0700` marker, and the S3
  contract waits for SeaweedFS cold-start on CI runners.

## [0.10.0] - 2026-09-15

### Added
- Deployment now supports both named Docker volumes and host bind mounts for
  durable instance state.

### Changed
- The Admin navigation now groups work into Overview, Upstreams, Cache, Logs,
  Security, and Projects, with instance-wide administration in the sidebar
  footer.
- Bandwidth reporting is part of Overview; metadata refresh and audit records
  are grouped with access logs.
- Security and quarantine views use compact labeled selectors, while preserving
  existing deep links and browser navigation.

### Fixed
- Development server entry documents no longer remain stale after a frontend
  rebuild.

## [0.9.4] - 2026-09-10

### Changed
- Cache cleanup plans now validate request bodies strictly, report bounded
  execution outcomes honestly, and keep the preview scope visible in the Admin UI.
- Access logs preserve and filter unknown cache outcomes instead of inferring MISS
  from legacy boolean fields.
- Warmup history distinguishes all-failed jobs from partial success and evicts
  the oldest terminal record when its bounded history is full.
- The package-install benchmark uses explicit registry endpoints and exact sample
  counts, while frontend dependencies pass the moderate-level audit gate.

### Fixed
- LRU cleanup previews now page with a composite `(last_accessed, id)` cursor,
  preserving candidates when database primary-key order differs from access order.

## [0.9.3] - 2026-09-09

### Added
- Runtime capability summaries, security capability status, access-log request
  details, and local redacted diagnostics for operator troubleshooting.
- Read-only cache cleanup previews with execution bound to the reviewed plan.
- Bounded asynchronous cache warmup jobs with progress, retry, cancellation,
  restart recovery, and persisted history.
- Read-only MCP request explanations with bounded and redacted request facts.
- A reproducible package-install benchmark harness and upgrade qualification
  coverage.

### Changed
- Dashboard cache rates exclude requests whose cache outcome is unknown.
- Warmup and cache-miss UI states now distinguish accepted, completed, and
  failed work without presenting unknown outcomes as success.

### Fixed
- Diagnostic output now bounds and redacts request facts and sensitive URLs.
- Warmup history fails closed when persisted state is corrupt or unavailable;
  unreadable state is never replaced with an empty history.
- Schema-v4 upgrade and release verification coverage now exercises the current
  migration and recovery boundaries.

## [0.9.2] - 2026-09-02

### Upgrade notes
- Schema v3 does not reinterpret concrete Package Rules created by the legacy
  shared comparator. A v0.9.1 `created_by = security-scanner` value is not
  trusted as machine provenance because it can also be a human username.
  Before upgrading, review and remove every concrete rule, including generated
  auto-block rows, plus ecosystem-wide rules for unsupported proxy surfaces;
  recreate reviewed rules after upgrade. The migration then disables all
  stored automatic-block policies. See `docs/package-rules.md`.
- Schema v3 re-derives PyPI, Go, Cargo, CRAN, and Maven cache identities from
  authoritative keys and clears ambiguous metadata guesses. APT and NuGet
  cache identities are cleared because their transport keys cannot prove the
  package identity required by OSV; pre-v3 npm cache rows cannot participate in
  a new scan. Because legacy storage cannot distinguish fetched advisories from
  operator imports, all stored advisories, checks, dismissals, and project
  package rows for npm, PyPI, APT, Go, Cargo, Maven, NuGet, CRAN, RubyGems,
  and Composer are invalidated before trusted identities are scanned or
  imported again.
  Cached package objects remain available.
- Legacy Hugging Face cache rows are retired during migration and synchronously
  removed with their stored objects before request handling; ref pins are
  invalidated so a case alias cannot retain stale private bytes.
- Legacy malicious-package data is migrated only for the six end-to-end
  covered ecosystems. Unrecoverable npm and unsupported-ecosystem rows and
  overrides are removed, recoverable identities are dialect-normalized, and a
  full blocklist resync is required.

### Changed
- Package Rules now validate and persist ecosystem-specific package and
  version identities at creation time. PyPI uses PEP 440; Cargo and npm use
  strict SemVer comparisons, with npm exact/single-comparator rules enforced
  only after its signed packument provenance is authenticated. Go, Maven,
  NuGet, Conda, CRAN, and Alpine are exact-only; APT and Composer remain
  package-only where request paths do not prove a complete version. Unsupported
  ranges are rejected instead of guessed.
- Minimum-release-age enforcement is safety-disabled. Startup rejects positive
  enabled thresholds until the selected artifact source and authoritative
  timestamp provenance are bound end to end. New permanent approvals are
  disabled, while historical approvals remain readable and revocable.
- Guaranteed malicious-dataset request-path coverage is npm, Cargo, Composer,
  NuGet, Go, and Maven. PyPI and RubyGems are deliberately excluded until every
  served artifact format has complete identity provenance.
- NuGet background OSV scanning is disabled until registry canonical package
  IDs are retained instead of inferred from lowercase flat-container paths.
- APT background OSV scanning is disabled until authenticated repository
  metadata binds each binary package and served filename to Debian's source
  package identity.
- RubyGems background OSV scanning is disabled until trusted index or gemspec
  provenance is persisted; platform artifact filenames are not parsed into a
  guessed identity that could record a false-clean result.
- PyPI background OSV scanning trusts simple-index project names and strictly
  parsed PEP 427/625 artifacts only. Ambiguous legacy archives are retained in
  cache but are not assigned a guessed package identity or recorded as clean.
- Composer background OSV scanning now trusts only exact per-package `p2`
  metadata and reversible dist cache keys. `packages.json`, incomplete or
  malformed metadata paths, indexes, and arbitrary passthrough paths remain
  cacheable but cannot acquire a guessed package name or clean OSV receipt;
  legacy guessed Composer identities require migration repair before scanning.
- Cargo and CRAN background OSV scanning now trusts strict artifact keys only.
  Cargo sparse-index files, CRAN package indexes and installers, and ambiguous
  paths remain cacheable but cannot create a guessed scan or false-clean result.
- Automatic OSV-to-Package-Rule blocking is safety-disabled for every
  ecosystem until complete affected sets, including ordered ranges,
  prereleases, and explicit versions, can be represented without loss. Manual
  reviewed selectors remain available wherever the Package Rule capability
  table permits them, including single comparators for PyPI, Cargo, and npm.

### Fixed
- Frontend build tooling now pins Browserslist 4.28.8, which contains the
  upstream fixes for its unbounded query caches and unsafe custom-stats
  normalization.
- Pre-release, epoch, post-release, qualifier, revision, and build-metadata
  ordering no longer falls through a dot-splitting cross-ecosystem version
  comparator. Invalid Package Rule coordinates now fail before persistence,
  and semantic/integrity failures return `PACKAGE_POLICY_UNEVALUABLE` instead
  of silently allowing a request.
- Permanent quarantine approvals now use each ecosystem's package and version
  equality, collapse semantic aliases, and reject invalid coordinates.
- First-run setup now commits the administrator and onboarding state before
  publishing `config.toml`. SQLite setup commits use a pinned `FULL`-
  synchronous connection, config replacement is `0600` + temp-file fsync +
  atomic rename + parent-directory fsync, and startup reopens the wizard when
  a configured database has no loginable administrator. Fault-injection tests
  cover each database and filesystem publication boundary.
- Policy winner selection now uses an explicit lexicographic specificity tuple
  (reserved priority, selector levels, deny tie-break, and stable database ID)
  rather than an additive score or Store row order. The read-only Admin policy
  test shows matching candidates, winner, match levels, precedence reason, and
  policy snapshot state.

## [0.9.1] - 2026-09-01

### Upgrade notes
- Stop a running v0.9.0 background daemon with the v0.9.0 binary before
  replacing it. v0.9.1 refuses the unauthenticated PID-only record used by
  v0.9.0; if replacement already happened, verify that process has exited
  before removing the legacy PID file manually.
- The official container now runs as UID/GID `10001:10001`. Operators reusing
  a root-owned named volume must perform the documented one-time ownership
  change. Operators using the exact bind-mounted Compose layout shipped in
  v0.9.0 must use the Linux-only, fail-closed compatibility procedure in
  `docs/deployment.md`.
- Non-loopback listeners now require a JWT signing secret of at least 32 bytes
  with no surrounding whitespace. The v0.9.0 Compose preparer preserves an
  already-strong secret, or supports an explicitly confirmed rotation from a
  weak legacy secret using `DEPSILO_ACCEPT_JWT_ROTATION=1`. Rotation invalidates
  existing browser JWTs and requires a new login, but preserves passwords and
  API tokens. Signed extra-PyPI
  metadata must be fetched online once after rotation; cached artifact objects
  remain reusable.
- S3 writes larger than 8 MiB now use multipart upload. Restricted bucket
  policies need the normal object-write permissions plus
  `s3:AbortMultipartUpload` so failed writes can be cleaned up.

### Added
- First run now supports a zero-config installation with unified durable state,
  a startup summary, a single-volume `compose.yaml`, and generated bootstrap
  credentials.
- Portal and Admin now guide Operators through connecting and verifying their
  first real package-manager project.
- Hugging Face proxying now supports the Xet read-token routes used by current
  `huggingface_hub` and `hf-xet` clients without caching token responses.
- Release qualification now blocks artifact publication on all 14 official
  package-manager clients, the Docker Registry client, pinned ccache and
  sccache clients, a real MinIO S3 storage contract, and a v0.9.0 state-reopen
  contract covering config, SQLite identities, passwords and tokens, real npm
  metadata, cache metadata, and offline cached artifacts. A second contract
  runs the immutable published v0.9.0 image with its exact shipped Compose bind
  layout, then proves the fixed-UID release candidate preserves that state.
- Browser accessibility coverage now derives every Admin route from the route
  manifest, while Portal tests enforce the anonymous public-API boundary and a
  single owner for status polling.

### Changed
- Minimum-release-age quarantine and the known-malicious blocklist now offer an
  explicit `warn` mode that records the policy match while serving the request;
  `block` remains the default.
- The minimum toolchains and container bases are Go 1.26.7, Node.js 22.23.2,
  and Alpine 3.23.5; Go networking/crypto modules were refreshed with the
  corresponding security fixes.
- The official container now runs as fixed non-root UID/GID `10001:10001`
  while preserving `/root/.depsilo` as its state-volume path. The bind layout
  shipped in v0.9 has a fail-closed migration that backs up durable state and
  rejects symlinks, hard links, special files, and nested mounts before changing
  ownership; other older root-owned named volumes use a separate documented
  one-time ownership migration.
- Daemon startup now uses the effective serve configuration, waits for actual
  dependency readiness, reports a private startup log, and fails non-zero when
  the child exits or never becomes ready. Daemon records bind a PID to its OS
  process-creation identity, starts are serialized, Unix children enter a new
  session, and Windows uses a per-start named shutdown event. Graceful shutdown
  has a ten-second deadline, and foreground `start` shares the `serve` lifecycle
  and flags.
- `depsilo doctor` now checks the database and storage details reported by
  `/ready` and fails when either dependency is not ready.
- Release promotion is serialized repository-wide, binds lightweight or
  recursively peeled annotated tags to the triggering commit, and rechecks the
  authoritative ref plus the monotonic floating-tag decision immediately before
  registry mutation and GitHub Release publication.

### Fixed
- npm metadata rewriting now changes only package `dist.tarball` fields instead
  of rewriting unrelated strings that happen to contain an Upstream URL.
- Docker Registry authentication now propagates cancellation, locally
  validates registry, Bearer-realm, and redirect DNS scopes before direct dials
  and every explicitly proxied request, strips credentials on cross-origin
  redirects, bounds response bodies, preserves token parameters, and supports
  long streamed blobs without a whole-response timeout. Explicit forward-proxy
  DNS and ACL policy remains the documented final egress boundary. Docker tag
  pagination, NuGet search queries, and npm `Accept` representations now preserve
  requests upstream and use representation-safe cache keys.
- S3 storage signs non-seekable payloads, accepts known- and unknown-length
  streams with bounded buffering, aborts incomplete multipart uploads, creates
  regional AWS buckets correctly, and supports object-only credentials for an
  existing bucket.
- Package-cache and compiler-cache S3 bucket, endpoint, region, access-key, and
  secret-key settings now honor explicit `DEPSILO_*` environment-only
  configuration without requiring matching keys in `config.toml`.
- Hugging Face canonical relocation from `hf-mirror.com` to the official origin
  preserves the exact path and query, revalidates each hop, and permanently
  removes authorization after crossing origins.
- Generated and example configurations no longer enable a localhost forward
  proxy for every built-in Upstream.
- Remote listeners reject empty, short, whitespace-padded, or placeholder JWT
  signing secrets. Development and daemon logs that can contain first-run
  credentials are private.
- Portal Monitor no longer starts a second `/api/v1/stats` poll, and the shared
  Admin breadcrumb no longer exposes invalid or duplicate navigation semantics.

## [0.9.0] - 2026-08-12

### Added
- **Compiler cache service**: adds namespace-scoped remote caching for official
  ccache HTTP and sccache WebDAV clients, with read-only/read-write build
  credentials, bounded uploads and downloads, global/per-namespace quotas,
  LRU eviction, local or S3 storage, Admin management, Prometheus metrics, and
  an opt-in real-client regression target.
- **Admin control plane remediation**: Settings now atomically preserves and patches
  `config.toml` with explicit immediate/restart/environment-override results; ordinary
  upstream CRUD is database-authoritative and atomically updates live proxy Pools; fresh
  Principal authorization, exact Admin DTOs, responsive Base UI wrappers, and Playwright/
  axe regression coverage make all 13 Admin routes permission-aware and truthful about
  loading, empty, stale, error, and mutation states.
- **Tamper detection (DIRECTION T1)**: records the SHA-256 of each
  immutable artifact on first fetch; when a natural re-fetch (background
  refresh) yields different upstream bytes under the same version,
  depsilo keeps the trusted first-seen copy, refuses to cache the new
  content, and fires a critical `tamper_detected` webhook + audit event.
  Alert-only (never blocks); the hash is computed in the existing storage-pump
  reader without a second pass; immutability is inferred from TTL (no adapter
  changes). `[supply_chain.tamper_detection] enabled` (default true).
- **Known-malicious blocklist (DIRECTION Task 2)**: syncs the OSV
  malicious-packages dataset (MAL-* advisories) for eight ecosystems every 6h
  and refuses matched versions with HTTP 451 `MALICIOUS_BLOCKED` as the
  quarantine checker's step 0 — threshold-0 ecosystems (go) and the quarantine
  allow-list cannot bypass it. Audited operator overrides expire after
  24h and cannot be extended in place; continued access requires a new
  audited override. Critical-severity webhooks on every
  block; admin tab (sync status / manual sync / overrides) on the
  Quarantine page; enabled by default with degrade-on-failure (sync
  errors keep blocking on the last good dataset). `mirror_url` +
  `proxy` configurable for restricted-egress deployments.
- Go modules gained the malware gate on `@v/*.zip` downloads (with
  GOPROXY `!upper` path decoding); extra PyPI-compatible indexes
  canonicalize to the pypi blocklist rows.

### Changed
- **Minimum release age now defaults off** for new and empty configurations.
  Set `[supply_chain] min_release_age_enabled = true` to activate the
  recommended thresholds, or `false` to disable the gate while retaining a
  threshold table. Existing configs with explicit thresholds and no switch
  remain enabled for backward compatibility. The known-malicious blocklist
  and tamper detection are unaffected.

### Fixed
- Public service status now reflects actual Upstream health instead of always
  reporting healthy. Public latency history is keyed by stable Upstream ID so
  same-named sources in different ecosystems no longer share a graph;
  pre-ID rows remain isolated under `legacy:<name>` keys.
- Unified interactive password validation across setup and Admin user
  mutations, and added persistent JWT credential generations so password,
  role, and enabled-state changes immediately revoke older login sessions
  without revoking API tokens.
- Active Upstream workers now probe immediately at startup instead of waiting
  for the first interval. When every standard-pool source is unhealthy,
  passive sources receive a cooldown-limited half-open request so ordinary
  network failures can recover without admitting critical protocol failures.
- Built-in Upstreams now use canonical, live endpoints for RsProxy, Maven
  Central, TUNA RubyGems, and the Aliyun Composer mirror. Exact legacy seeded
  adapter/name/URL triples are upgraded once on restart; custom URLs and all
  other operator-managed fields are preserved.
- Blocklist import semantics hardened by adversarial review against
  the LIVE dataset: bounded-range advisories without version lists
  (fsevents, @solana/web3.js) are skipped instead of blocking every
  clean release; withdrawn advisories are never imported; NuGet names
  lowercase to match the flat-container protocol; a zero-entry archive
  can no longer wipe a populated ecosystem; all downloads and parsing finish
  before per-ecosystem transactional replacement, and sync is re-entrancy guarded.
- Quarantine/malware webhook timestamps were the zero value (rendered
  as year 0001 in chat channels).

## [0.8.0] - 2026-07-09

The "enforcement layer" release: Depsilo pivots from cache-with-extras to a
supply-chain enforcement layer (ADR-0003 / ADR-0004) and ships its first
enforcement primitive.

### Added
- **Supply-chain quarantine (minimum release age)**: package versions younger
  than a per-ecosystem threshold (npm 7d, most ecosystems 3d, go/apt exempt)
  are refused with HTTP 451 `QUARANTINED`. Not-found and unsupported publish
  timestamps fail closed by default; genuine upstream outages allow with a
  warning. The allowlist supports glob / exact pin / version ranges; blocks,
  bypasses, and admin approval/revoke actions are audited, while block decisions
  fire webhooks (Slack / DingTalk / WeCom / Feishu). Wired into 13 adapters via
  publish-time resolvers (8 real registry APIs + Last-Modified approximation for
  the rest). Ships enabled with safe defaults — an empty config is protected.
- **Composer dist mirroring**: `packages.json` now injects a preferred dist
  mirror so archives flow through the proxy — previously Composer downloads
  went straight to GitHub (no cache, no quarantine). Dist serving requires
  version *and* reference to match upstream metadata (metadata drift 404s and
  Composer falls back to the origin URL with the correct bytes); the
  quarantine resolver understands `~dev` metadata. Known limit documented:
  Composer treats a mirror 451 as a failed mirror and falls back to the
  origin, so this gate is caching + audit + best-effort blocking.
- `/api/v1/stats` gains a `week` block (7-day rolling window) so portal value
  metrics no longer reset at midnight.
- Portal redesign: dual-value hero
  ("Install fast. Install safe."), frosted film-grain background, chromatic
  shimmer on the primary CTA, prompt preview with filename/line numbers,
  hi-res (2560px) layout support, health-first Monitor with a quick filter
  ("/" to focus; matches ecosystems, aliases, upstream names/URLs) and 7-day
  value chips.
- CLI: `serve --port / --host / --config / --log-level` plus 12-factor
  `DEPSILO_*` environment variables.

### Changed
- **Pricing reset**: audit logs, package allow/deny rules and security
  scanning are now open-source; Pro narrows to multi-project workspaces +
  per-project SBOM export (contract-priced, Lemon Squeezy self-serve removed).
- "Instrument" design system (signal-green, dark default) across portal and
  admin; display typography, contrast and micro-interactions tuned in an
  aesthetic review pass (WCAG-AA small text, CJK-aware heading tracking).
- Heartbeat ticks: red now strictly means *down*; slow-but-alive upstreams
  paint amber, thresholds unified with status dots at 150ms.
- Integration prompt rewritten for transparency (no hidden product naming);
  release + image pipelines now dogfood CycloneDX + SPDX SBOMs. These artifacts
  are currently unsigned; cosign keyless signing remains planned.

### Fixed
- Integration test suite was fully red: quarantine's fail-closed default
  reached real registries for mock packages; thresholds now zeroed in the
  test config. A rules test still asserted pre-pricing-reset Pro gating.
- Admin sidebar layout jumps from Material Symbols FOUT; missing dashboard
  error-state i18n keys; stale Pro badges on unlocked pages.
- Alpine ships default upstreams (tuna / dl-cdn) and config-writer support.

## [0.7.1] - 2026-06-23

### Fixed
- Release pipeline (`.github/workflows/release.yml`) was inherited from the deprecated Wails desktop spec and started failing on every tag push: Ubuntu 24.04 (`ubuntu-latest`) dropped `libwebkit2gtk-4.0-dev`. Replaced the three Wails jobs (`desktop-macos`, `desktop-windows`, `desktop-linux`) with tray packaging that matches v0.7.0's actual deliverables.

### Added
- Release pipeline now publishes `depsilo-tray-macos.zip` (`.app` bundle, unsigned) and `depsilo-tray-linux-amd64.tar.gz` (tray binary + `.desktop` launcher + `install-linux.sh`) on every `v*.*.*` tag. Linux build pulls `libgtk-3-dev` + `libayatana-appindicator3-dev` (not webkit).

### Removed
- `cmd/server/main_desktop.go` (Wails entry, no frontend bindings), `wails.json`, the `build-desktop` Makefile target, and the `//go:build !desktop` tags on `cmd/depsilo/main.go` + `cmd/server/main_server.go`. `go mod tidy` dropped `wails/v2` and 20 transitive deps; `fyne.io/systray` is now a direct dependency.

## [0.7.0] - 2026-06-23

### Added
- **macOS menu-bar app** (`cmd/depsilo-tray/` + `internal/tray/`): live status icon shows healthy / degraded / failed with hit rate. Menu: Open Admin / Open Portal / Run Doctor / Quit. `make app-macos` packages a `Depsilo.app` bundle with `LSUIElement=true` (status-bar only, no Dock entry).
- **Linux desktop integration**: `make install-linux` writes the binary to `~/.local/bin` and a freedesktop launcher; `make autostart-linux` enables login start. GNOME on Wayland users need the AppIndicator GNOME extension.
- **`depsilo doctor`** CLI: end-to-end self-diagnosis (service / status / version / storage / upstream health / hit rate) with coloured TTY output (honours `NO_COLOR`), `--json`, and exit-code 1 on FAIL. Designed to surface "which signal is bad" with actionable hints instead of dumping stats.
- **`depsilo init-agent`** CLI: writes `CLAUDE.md` / `AGENTS.md` / `.cursorrules` with a marker-bracketed Depsilo block so AI coding agents auto-detect this project's proxy. Idempotent — preserves user content outside the markers.
- **MCP server** at `POST /mcp`: JSON-RPC 2.0 over Streamable HTTP. 6 tools (`depsilo_status`, `depsilo_doctor`, `depsilo_configure`, `depsilo_search`, `depsilo_recent`, `depsilo_warmup`) + 2 resources (`depsilo://discover`, `depsilo://stats`) + 1 prompt (`setup`). Claude Code / Cursor / any MCP-aware client can drive Depsilo via structured tool calls instead of parsing free-form prompts.

### Changed
- Brand palette switched from purple (oklch hue 295) to deep teal (oklch hue 200) across brand / aurora / page-wash / grad-rim / shadow tokens, light and dark.
- Default sans font changed from Inter Variable to Geist Variable; mono from JetBrains Mono Variable to Geist Mono Variable. Noto Sans SC retained as the CJK fallback so Chinese glyphs do not shift.
- Admin login rebuilt as a split-card: brand panel (13-ecosystem logo wall + 3 live stats from `/api/v1/stats`) on one side, form on the other. Card adds aurora-rim top accent + shadow-card + radius 14.
- Pro paywall modal redesigned: workspace_premium brand icon badge + 4-bullet Pro feature preview + footer reorganised with primary action anchored right.
- `ModalV2` lifted across all 20+ admin call sites: ESC closes, `role="dialog"` + `aria-modal` + `aria-labelledby`, viewport-centred (was `mt-[20vh]`), 160ms scale + fade entry with `prefers-reduced-motion` fallback, tokenised backdrop, larger `--shadow-pop` elevation.
- Portal hero gained a 13-ecosystem logo wall under the subtitle (clickable shortcuts). Portal `CodeBlock` now highlights URLs in brand teal and dim-renders comment lines.
- BandwidthReport's 12-colour rainbow chart palette replaced by a single shared `web/src/lib/ecosystemColors.ts` mapping ecosystems to their real-world brand colours (npm red, go cyan, cargo orange, …). CacheManage uses the same map.
- `README.md` "Use with AI agents" section restructured into three tiers (`init-agent`, MCP, copy-paste prompt) with examples.

### Fixed
- Five dead CSS tokens silently falling back to browser defaults: `var(--text-base)` (5 sites in MainLayout), `var(--border-purple)` (Button secondary + ProRequiredCallout), `var(--lemon)` (Security severity bars, Projects token warning, Dashboard cache-usage banner). All replaced with real tokens.
- Admin top-bar service status indicator was hardcoded `<StatusDot status="healthy" />` with a bare English "Healthy". Now reads `stats.service.status` and renders three-tier health (healthy / degraded / failed) via i18n.
- `Settings.tsx` webhook tab passed `icon: '🔔'` (an emoji) as a Material Symbol name, rendering as a fallback glyph. Changed to `'notifications'`.
- Six leftover `rgba(83,58,253,...)` hardcoded purple residues from before the brand switch — Button secondary hover, ProRequiredCallout backgrounds, Settings tab active state, SetupWizard ecosystem checkboxes.
- `MainLayout` `JSON.parse(localStorage.getItem('user') ...)` no longer white-screens admin on corrupt data; wrapped in try/catch with a typed fallback.
- 13 hardcoded green hex (`#10b981` / `#3bd671`) replaced with `var(--ok)` across Dashboard, BandwidthReport, AuditLogs, AccessLogs, UpstreamCard, Portal Monitor. UpstreamCard `beatColor` 4-tier palette collapsed to a 3-tier ok / warn / danger semantic.
- 12 inline `onMouseEnter` / `onMouseLeave` handlers doing hover via JS (which silently broke `:focus-visible`) converted to Tailwind `hover:` utilities across MainLayout, AccessLogs, AuditLogs, CacheManage.

### Removed
- Three orphan Portal pages (`PackagesV2.tsx`, `PackageDetailV2.tsx`, `LiveStreamV2.tsx`) and their i18n namespaces — none were wired to a route.
- `AgentPane` component (the Portal `LanguageRail` dedup made it unreachable; the per-language AI tab already covers the same workflow).
- 21 em-dashes across i18n, UI text and CLI tool output, in line with the taste-skill audit's hard ban.

## [0.6.0] - 2026-06-21

### Added
- Webhook notification system: Slack / DingTalk / WeCom / Feishu / Generic transports. Configurable per-event in Settings → Webhooks.
- Background scheduler: periodic upstream health checks and license-expiry warnings.
- `depsilo backup --out file.tar.gz` and `depsilo restore <archive>` CLI for config + database snapshots.
- One-liner installer script (`install.sh`) and Homebrew formula template.
- Goreleaser release pipeline producing per-platform binaries.

### Changed
- README documents four installation methods (binary download, install.sh, Homebrew, Docker).
- `Upstream.IsHealthy()` is now thread-safe.

### Fixed
- Goreleaser entry point was `cmd/server` instead of `cmd/depsilo`; automated release binaries now include the CLI subcommands.

## [0.5.0] - 2026-05-28

### Added
- HuggingFace Hub adapter — proxies models + datasets via `/huggingface/*`. Supports the full huggingface_hub client surface (huggingface-cli, transformers, datasets) by honoring `HF_ENDPOINT`. Server-side 302 following streams LFS blobs from `cdn-lfs.huggingface.co` inline; clients never see the signed CDN URL.
- New `[[huggingface.upstreams]]` config block with `hf-mirror.com` + `huggingface.co` defaults; the priority selector uses the first healthy source.
- Pass-through Authorization handling: gated models work when users provide their own `HF_TOKEN`, but auth'd responses are NOT cached (cross-user safety).
- New unit tests: 4 keyer + 5 resolver. New integration tests: 7 covering metadata, tree listing, dataset metadata, resolve (200 + 302), HEAD-only, unknown path. New Docker E2E (`make test-docker-huggingface`) using `prajjwal1/bert-tiny`.
- Portal QuickStart: "Hugging Face" entry with `huggingface-cli` and `transformers` setup snippets.
- `/api/v1/discover` and `/api/v1/agent-prompt` know about HuggingFace.

## [0.4.0] - 2026-05-21

### Added
- `/admin/license` page for self-serve 14-day Pro trial activation and runtime license-key management (set / change / remove)
- API endpoints: `POST /api/v1/admin/license/trial/activate`, `PUT /api/v1/admin/license/key`, `DELETE /api/v1/admin/license/key`
- New backend modules: `internal/trial` (local 14-day state machine) and `internal/entitlement` (façade combining license + trial)
- Frontend: global `ProRequiredModal` triggered by 402 responses, with inline "Start trial" CTA when available
- Landing-page `/pro-trial` page (closes the existing 404 from the Pricing CTA)

### Changed
- `GET /api/v1/admin/license/status` response body — new `source`, `days_left`, `trial_used`, `trial_available`, `license_key_masked` fields. Old `key_masked` and `activated_at` retained as deprecated aliases for one release; will be removed in 0.5.0
- `402 PRO_REQUIRED` response now includes a `trial_available` boolean
- `audit.Logger` and `rules.Engine` now depend on `entitlement.Checker` instead of `license.Manager` directly — trial users now get the same audit + rules behaviour as paid users
- `license.NewManager` signature now accepts `*gorm.DB` for runtime key persistence; DB-stored key takes precedence over `config.toml`

### Fixed
- `license.Manager.doValidate` no longer reads `m.key` outside the lock — eliminates a data race introduced when `SetKey` was added

### Deprecated
- `EntitlementStatus.key_masked` field — use `license_key_masked` (alias removed in 0.5.0)
- `EntitlementStatus.activated_at` field — derive from per-source state instead (alias removed in 0.5.0)

## [0.1.0] - 2025-03-30

### Added
- pip (PyPI) proxy and cache — production ready
- apt (Ubuntu/Debian) proxy and cache — production ready
- Web UI dashboard with cache stats and upstream health
- Prometheus metrics endpoint at /metrics
- Multi-upstream support with per-source HTTP proxy
- Automatic upstream health checks and failover
- Local filesystem storage backend
- SQLite database (default)
- Docker and docker-compose deployment
- MIT License