# PatchMon native Homarr contract and blocker This is source verification for #291, not functional or rendered acceptance. The worktree starts at committed `d956824f`; no dirty Immich proof work was copied. The reference owner must register `patchmonTarget()` in `targets.go` separately. Common reconciliation and ownership files are unchanged. ## Read-only credential boundary The existing Setup uses the official headless `auth/setup-admin` endpoint, saved Admin login and authenticated profile readback. An incompatible existing owner is not replaced. With Homarr installed, Setup records a retryable Manual step instead of creating a credential with unapproved authority. Late Homarr installation is covered by the descriptor's fresh, GET-only preflight before any Homarr mutation. Account Setup alone does not prove statistics readiness. Rerunning PatchMon Setup without Homarr records that credential step as skipped, replacing an earlier Manual hold rather than retaining stale Needs attention. Homarr requires **both** `patchmonApiKey` and `patchmonApiSecret`, sent as Basic auth to `/api/v1/gethomepage/stats`. A login JWT or generic API key is not a substitute. The matching PatchMon credential has metadata `integration_type: gethomepage`; general `api` credentials with `host: get` scopes cannot authenticate this endpoint. Creation/listing uses the official `/api/v1/auto-enrollment/tokens` endpoint and `can_manage_settings` permission. The once-returned secret cannot be retrieved from the token inventory. The actual image's `Enroll` handler checks activity, secret, expiration, IP ranges and daily limit, **but never integration type**. Thus GetHomepage credentials also grant host enrollment. Creation replaces a zero daily limit with 100; one-per-day still grants write authority. No supported minimum permission combination yields a read-only credential. This branch does not create, discover/adopt, rotate, delete or fabricate such a pair, nor treat a manually supplied pair as approval. No pair is stored or transmitted at runtime through this paused Link. Existing Project secrets remain unchanged. Retry requires a reviewed upstream separation of statistics and enrollment authority, an explicitly supported image contract, and owned credential provisioning/verification through helpers and the Project secret boundary. Do not bypass the hold with a browser token or broader credentials. Installing agents, enrolling computers and collecting inventory require a separate approved target. No host inspection, package upgrade, device privilege or external account operation is included. ## Native data contract The prepared descriptor uses `http://patchmon:3000` internally, independently of the browser Address. Its native widget kind is `patchmon`, with one integration; no host identifier is invented. Shared Stats keys are exactly `totalHosts`, `hostsNeedingUpdates`, `securityUpdates`, `totalOutdatedPackages`. The native query is authenticated `widget.patchmon.getStats`. Prepared verification rejects missing halves, non-200 responses, absent/null/ negative/noninteger counts, invalid timestamps and inconsistent source counts. The source read includes `awaiting_data_hosts`, which Homarr does not map: nonzero awaiting inventory remains Manual, not a successful empty dataset. Authenticated explicit zeros with zero hosts are legitimate empty inventory, not evidence of secure computers. The compliance hero and threshold colors are disabled to avoid a false healthy/100%-compliant empty-fleet presentation. Exact server semantics: | Field | Meaning | | ----------------------------- | ------------------------------------------------------------------------------------------------ | | `total_hosts` | All host records, including pending and nonreporting hosts | | `hosts_needing_updates` | Distinct hosts with a package marked `needs_update` | | `security_updates` | Distinct package IDs marked both update-needed and security-update, not host-package occurrences | | `total_outdated_packages` | Distinct update-needed package IDs across hosts | | `hosts_with_security_updates` | Distinct hosts with security-update packages | | `up_to_date_hosts` | Hosts with package data minus hosts needing updates | | `awaiting_data_hosts` | Hosts without package data | | `recent_updates_24h` | Successful full/partial inventory report rows in the last day, not installed patches | | `total_repos` | Active repositories | | `last_updated` | Statistics response generation time, not last host inventory time | Repeated fresh reads do not prove recent host reporting, installed patches, reboot completion or hardware security. OS distribution counts active host records by OS type/version; no OS widget or hardware-specific metric is enabled. Verification code is prepared but cannot run through the held preflight. ## Exact sources and registry identities OCI registry metadata was read over HTTPS without Docker on 11 October 2026; no image was started. These tags are mutable, not runtime compatibility proof. | Image | Index SHA-256 | linux/arm64 manifest SHA-256 | Config SHA-256 | | ---------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | | `ghcr.io/homarr-labs/homarr:v2.4.1` | `ee67a5e856c051c4e114418a54b89a3fc3cb5507d0689e5cb89c790305c549cd` | `308feba8b9845cbdb21eb2e1b05579d49fb815bf3f0a452e58b59fcd4db8ecd2` | `c6d895d659718b05ab9cc7b936458e6f5820b5cf3f0d35318588bd1b6901ee1b` | | `ghcr.io/patchmon/patchmon-server:2.1.3` | `9eba4008c8e25f72929752bba7499b5deda26cf5de3e4678dbf760d16822b2f2` | `54d587af0871a8d6369386c8d2e8830d20f03b69f51b19cf5c6cb5bf5356a2ed` | `e22cecbb743cdabffd50fbc6a06293c7ad29eacd41d0d08556c954305860904c` | Homarr's image revision label is `3d4150e59f600876b7e308f857b2f21572b75348`, with version label `main`. The reference tagged baseline is `ebbd4876d25a51151444a009ab596e1154d62880`. The integration definition, PatchMon adapter/types, native router/widget options and component, and shared Stats adapter/router/types are byte-identical between those revisions. Actual authenticated Homarr `info.getInfo` version remains unverified for this lane; the reference common version check is unchanged. PatchMon's revision label and `v2.1.3` tag both resolve to `354ec8cdeb53338e57a8332fb2ddd5c7f307c75c`; its version label is `2.1.3`. Primary sources: - [Homarr official integration guide](https://homarr.dev/docs/integrations/patchmon/) - [Actual Homarr adapter](https://github.com/homarr-labs/homarr/blob/3d4150e59f600876b7e308f857b2f21572b75348/packages/integrations/src/patchmon/patchmon-integration.ts) - [Actual native response schema](https://github.com/homarr-labs/homarr/blob/3d4150e59f600876b7e308f857b2f21572b75348/packages/integrations/src/patchmon/patchmon-types.ts) - [Actual Stats metric keys](https://github.com/homarr-labs/homarr/blob/3d4150e59f600876b7e308f857b2f21572b75348/packages/integrations/src/stats/existing.ts) - [Actual widget options](https://github.com/homarr-labs/homarr/blob/3d4150e59f600876b7e308f857b2f21572b75348/packages/widgets/src/patchmon/index.ts) - [PatchMon creation and enrollment handlers](https://github.com/PatchMon/PatchMon/blob/354ec8cdeb53338e57a8332fb2ddd5c7f307c75c/server-source-code/internal/handler/auto_enrollment.go) - [PatchMon auth/type check](https://github.com/PatchMon/PatchMon/blob/354ec8cdeb53338e57a8332fb2ddd5c7f307c75c/server-source-code/internal/middleware/api_auth.go) - [PatchMon route permissions](https://github.com/PatchMon/PatchMon/blob/354ec8cdeb53338e57a8332fb2ddd5c7f307c75c/server-source-code/internal/server/router.go) - [PatchMon count queries](https://github.com/PatchMon/PatchMon/blob/354ec8cdeb53338e57a8332fb2ddd5c7f307c75c/server-source-code/internal/sqlc/queries/dashboard.sql) - [PatchMon response construction](https://github.com/PatchMon/PatchMon/blob/354ec8cdeb53338e57a8332fb2ddd5c7f307c75c/server-source-code/internal/store/dashboard.go) ## Acceptance gaps This draft does not complete #291. Registry activation remains reference-owner work. Read-only credential provisioning is upstream-blocked. Fresh-image account Setup, negative live auth, populated/empty native statistics, rendered widgets, both install orders, restart, credential changes, user-resource preservation, removal and reinstall have not been exercised. No Docker, browser, installer, agent enrollment or patching was run. Existing focused and full local gates validate source/build behavior only; they are not API or lifecycle proof. Local verification passed with `GOFLAGS=-buildvcs=false`: focused tests for PatchMon/Homarr/Catalog, `go build ./...`, both vet runs including the e2e build-tag compile check, `go test ./...`, and race checks for PatchMon/Homarr/TUI/Activity. `gofmt -l .`, `git diff --check` and `bash -n install.sh` were clean. The e2e runtime was not enabled. ## Original-branch normalization, 13 October 2026 The original release branch forward-merges the SDK and additive Stats contracts without rewriting history. `verifyPatchmonStats` validates the four selected `count` metrics from the one central sample: finite nonnegative integers, hosts needing updates no greater than total hosts, and security packages no greater than outdated packages. Native verification and the source-only awaiting-inventory policy stay separate; independently cached native/Stats counts are not compared. Explicit zeros remain empty inventory, not secure/compliant computers. The #350 Manual credential hold still runs before compatibility or Homarr mutation, even if a manually supplied pair is present. The GetHomepage name and GET-only calls do not narrow its enrollment/script authority. A supported reviewed server credential separation remains required. Focused HTTP checks prove that the hold makes no Homarr or statistics requests; pure callback checks cover prepared Stats semantics only. No native refresh, rendered widget or runtime acceptance is claimed. The descriptor remains unregistered in this lane.