Komodo capability and efficiency audit — 2026-10-25¶
Verdict¶
Good foundation; not yet making full use of Komodo's useful deployment features. The highest-value work is dependency-aware deployment, verified execution ordering, and failure isolation—not additional alerts, more dashboards, or enabling every feature. Keep Komodo; improve how the existing resource model is used.
The original assessment below records the captured production baseline, not a
completed rollout. The owner subsequently authorized all high/medium findings;
the implementation status below supersedes their original recommendation status.
No workload deployment has been performed for this uplift. Historical guided
preparation cached the pinned helper image, created/scoped non-admin homelab-deploy
and verified the SOPS-encrypted API pair through read-only calls and the then-running
LAN runner at the legacy path. The first deploy on 2026-10-26 UTC subsequently failed
at credential loading before mutation during shared-parent permission changes.
PR #506 (6d6ff2e) and the credential correction #508 (6ab7660) are now merged;
the approved migration retained the existing pair unchanged. #510 review, controlled
activation and scoped credential/both-mode/natural-convergence acceptance completed
at 2026-10-26 19:05 UTC. Current phase is
edge_dispatch_blocked_github_queue: the owner-approved edge attempt remains queued
before job creation (see current checkpoint below). The authorized Reddit same-key fingerprint
refresh, all seven input checks, external Sync and least-privilege grants completed
on 2026-10-27 UTC and remain complete. Configuration bootstrap is complete, not workload rollout or
application acceptance; the separate operator-shell check-mode failure remains
unresolved. See the
bootstrap completion record
and activation procedure. The
earlier remote-main baseline 7e7f79b6 introduced manual-only dispatch; this
implementation preserves it. Live GitHub environment review/branch protection was
found absent, not assumed configured.
Implementation status after owner approval¶
The table describes the source uplift integrated onto 7e7f79b6 and merged as
6d6ff2e, with configuration bootstrap completed from reviewed main 824c148 on
2026-10-27; workload rollout is not complete. The journal records integration/CI,
migration/activation and bootstrap evidence. The manual GitHub registry-check path
succeeded independently of Komodo bootstrap.
| Finding | Implemented in source | Deployment / acceptance |
|---|---|---|
| 1: Actual deployment inputs | Six scoped private HMAC profiles; render-before-fingerprint; native tracked file; predeploy verification; local builds explicit; LiteLLM config baked into image | Five-profile preparation passed with six byte-identical outputs; subsequent same-key Reddit refresh and all seven input checks passed. Input declarations are live; real changed/unchanged application runs pending |
| 2: Result-aware relay | Exact Sync/Procedure Update completion, private credentials, persistent intent and host lock, full managed-resource visibility, busy checks, main-only truthful workflow | #508 credential migration unchanged; #510 activation and scoped credential/both-mode/natural-convergence acceptance complete. External Sync/grants complete; successful deployment remains pending. Not whole-host check-mode acceptance |
| 3: Selective apps | Orders/PR Dashboard conditional; Firefly conditional after rendering; force/recovery paths retained | Orders/PR Dashboard/Firefly input baselines prepared; deployment and deliberate image-refresh acceptance pending |
| 4: Failure isolation | Parallel host-local pull/deploy chains; earlier Firefly batch; independent app children; parent failure retained | Integrated failure simulation and live acceptance pending |
| 5: Reproducible version policy | Core/Periphery/helpers pinned 2.3.4; unused tag variable removed; exact LXC installer recipe; explicit targets intentionally retained | All four live versions verified 2.3.4; helper cached; Compose pin application pending |
| 6: Guardrails | Recursive graph/reference/policy validation, disabled/unknown operation rejection, dependency ordering and corrected shell guard; CI regression suites | Integrated regression validation pending; no production hook invocation performed |
| 7: Ownership/reconciliation | Explicit timezone and linked-Repo Sync bootstrap; new daily native image check replaces the disabled legacy schedule; missed-trigger deployment recovery explicitly manual | Reviewed external Sync adopted declarations and native-check schedule; no scheduled full deployment introduced |
The earlier uplift contained 20 Procedures, including 7 internal/general chain Procedures, 3 bootstrap-maintenance Procedures and scoped deployment/retry/canary paths. The extension is now bootstrapped: 26 live Procedures + 2 Actions (6 Repos and 21 Stacks unchanged). The 19 automatic Stack targets in the general graph remain conditional and selected once; the three image cohorts are separate manual roots. See the operating and rollout runbook and implementation journal.
Broader shared-environment exposure and sampling work remains outside this implementation; only the new dispatch token has a separate Core-only environment. Native tag selection was evaluated but not adopted: explicit names preserve host/app boundaries and are checked recursively. New resource additions need external reviewed bootstrap Sync because the relay refuses to equate hidden resources with absent ones.
Current overlay — fleet API routing recovered; edge still quarantined¶
Recovery applied 2026-10-27 10:03:15 UTC. The 08:14 LAN hold containing orphan
edge run 40469274207 also blocked fleet dispatch's Validate job because
RUNNER_FLEET_API_RUNS_ON selected LAN. Fleet run 40472345328, created 08:16 UTC
and canceled 09:31 UTC, had Validate queued with no runner/steps and downstream
jobs skipped. This was collateral routing blockage, not proof that nothing could place:
general CI Security jobs actually placed at 09:17 UTC; Hyper-V guests going offline
after 900s idle is expected. Gmail Python/Maintenance jobs succeeded separately;
other overall CI failures remain unfixed and outside this work. Earlier fleet run
40469102836 resumed only the exact Gmail CI runner; 40471032755 was a Gmail Docker
drain dry run only, not a fleet-wide drain. Official GitHub status was all operational
at inspection; that does not rule out an individual ghost run.
- Only repo
RUNNER_FLEET_API_RUNS_ONchanged to["self-hosted","Linux","X64","homelab","ci","homelab-ci-02"]. The prior LAN value is backed up locally inartifacts/cutover-20261026/fleet-api-routing-recovery-20261027.json.DEPLOY_RUNS_ONremains LAN;RUNNER_FLEET_MAINTENANCE_RUNS_ONremains["self-hosted","Linux","X64","runner-management"]; noCI_RUNS_ONchanges. No host/VM, restart, label, permission or credential changes accompanied recovery. - Direct proof: health run 40485555074
succeeded with
scope=homelab,dry_run=true,authorize_disruptive=false. Validate 17s and Observe 22s both actually ran onhomelab-ci-02; other mutation routes were skipped. The report was 7 online, 2 offline, 9 discovered, 10 expected, 3 drifts: successful read-only collection, not clean fleet-health acceptance. - Fleet operations are usable again; do not automatically rerun an unknown canceled mutation. The API selector covers validation, observation, planning, scheduled checks and guarded watchdog. It does not make every selected job read-only: watchdog apply can mutate only under its existing guarded approvals.
- This temporary exact general-canary selector removes the held-LAN dependency, not the Hyper-V/scaler or single-instance dependency. It is not a new always-on or redundant architecture; preserve the override until approved reassessment.
- Edge run
40469274207remains queued with zero jobs, deployment workflow disabled, and normal Ansible timers active. The broad LAN hold is historical: at 10:49:47 UTC exact-run quarantine replaced it. GitHub verifies the runner online and non-deploying qualification successful; normal LAN jobs restored. No edge acceptance/deployment or global outage resolution follows from either recovery; the API route remains unchanged.
See the parent recovery journal and canonical routing runbook.
Current checkpoint — edge dispatch blocked before job creation¶
2026-10-27 — edge_dispatch_blocked_github_queue. After PR #517 merged, the owner
approved an actual deploy-reviewed-images-edge attempt, not merely preparation.
Run 40469274207, attempt 1,
head 35f9a67d218f78251cedb2dd26a1e1c9edfceee1, was accepted at 07:47:46 UTC.
- The workflow was briefly enabled for dispatch, then immediately disabled. A bounded 20.5-minute watch ended with the original run still queued, zero jobs and zero check runs. A 60-second re-enable did not advance it. Immediate disablement may have contributed; cause is unconfirmed. This is not evidence of a server outage or runner misconfiguration.
- Cancel and force-cancel both returned HTTP 409:
Cannot cancel a workflow run that has not been queued yet.In-place rerun returned HTTP 403,This workflow is already running. No retry was accepted and no new dispatch occurred. The run remains unresolved/queued, not cancelled or completed. - Verified host
/opt/homelabHEAD remained0f522f0, with no relay pending intent and no edgeRunProcedure; only the oldCreateProcedureUpdatewas present. Sync remains at bootstrap824c148. Edge applications and Core/Periphery had no container ID/start-time changes. Edge execution is not accepted and not a no-op: the job never started. - Historical broad containment — 08:14 UTC, superseded at 10:49:47 UTC: deployment
workflow was
disabled_manually; owned drain marker/var/lib/github-runner/drain/homelab-lan-01recorded40469274207. The idle LAN runner was stopped after GitHubbusy=falseand noRunner.Workerwere verified; service failed/stopped, runner container absent. No job was interrupted. - Both Ansible pull timers were restored active and the owned
/etc/homelab/MAINTENANCEremoved. Do not strand convergence. Runner qualification, bootstrap and grants remain complete: 51 Read + 5 Execute, 26 Procedures + 2 Actions.
Current containment — 2026-10-27 10:49:47 UTC: the tested job-start hook and
fail-closed systemd pre-start verifier replaced the broad drain with exact-run
quarantine. The hook rejects the orphan and missing/invalid/non-homelab identity;
verifier protects hash, modes, mount/launcher contract and hook configuration. Only
the owned drain marker was removed after tests and backed up root-private. The LAN
service is active; actual container runs as 1001:1001 plus group 10002, with
correct hook/read-only mount and no Docker socket. GitHub verifies homelab-lan-01
online. Single non-deploying qualification run 40491873440
on main 54ca4da and exact LAN job 122148472245 both completed SUCCESS, verified
via GitHub API, including Set up runner and Run Linux and macOS qualification checks.
Normal LAN job execution is restored. Workflow remains disabled; ansible-pull-check.timer and
ansible-pull-apply.timer active, MAINTENANCE, relay pending.json and drain absent. See the guard contract.
Next operator action: reconcile the edge orphan
through inspection/GitHub support. Earlier browser access returned 404 despite
authenticated gh API access; UI cancellation was unverified. No further cancel/rerun
retry, redispatch, workflow enablement or UI Procedure/Stack bypass. Retain the guard
after restoration: removal requires proof the orphan is terminal with no executable
pending work and deliberate reviewed follow-up. The owner merged docs-only recovery
PR #519 as 4b2cb0c; rollout holds remain.
Health limits — 2026-10-27 10:53:25 UTC: 49 running including the restored runner,
38 healthy, 11 without healthchecks, zero unhealthy/restarting; earlier DNS/TLS smoke good,
not authenticated application/data acceptance. OneUptime and adapter October 27 B2
gaps are closed by explicitly owner-approved exact-snapshot copies; see the
completed catch-up receipt.
PR #520's local-retention fix
is merged, but affected live units still lacked --retry-lock=5m at the 11:55 preflight.
Normal convergence and next scheduled-run validation remain pending; no forced apply,
restore/retention acceptance or broader mutation permission. Image check safely failed closed
at 13/14 stacks, 32 services: Rexit latest exporter + studio versus deployed studio,
native prefers deployed_services, not an auto_pull issue. Gmail Docker variable
absent/proposed Docker runner unregistered is separate provisioning; the aiProject
August 19 ghost predates and was not caused by this work. Normal work may resume,
without mass restarts or replay of unknown canceled work. See
health limitations.
Historical checkpoint — bootstrap and least-privilege grants complete¶
2026-10-27 07:16:31 UTC — bootstrapped_deployment_pending. The owner-authorized
bootstrap package completed; the canonical completion record
records the private receipts.
- Reddit received a same-key fingerprint-only refresh, no env render/key change;
all seven input
--checkcalls passed. - One external
RunSyncused existinghomelab-deployscoped Execute. Exact Update6ae04f5aa10ff54a5801dc65, accepted at 07:14 UTC, completed successfully at 07:14:52 UTC, applying reviewed main824c1487735a5ea0b8d25a1aafab8f0362d6fa92. Resource SHA-2562551c36fe2b846ab5123e0c52f1a6823cc36f2c6ac3bbeaa2b84c0ca8e8b1c50was unchanged from #513; the preview was 18 creates + 13 updates, no deletes/deploys. Synclast_hash=824c148, pending resource changes/deploy targets 0, no errors. - Existing admin browser/native UI API added exactly 18 per-resource grants (15 Read + 3 Execute), leaving the original 38 unchanged. Root bot readback at 07:16:31 UTC verified 56 scopes = 51 Read + 5 Execute, unchanged non-admin identity and empty user-specific/base permissions; no admin or Server/Build creation rights were added. Live sets: 26 Procedures, 2 Actions, 6 Repos, 21 Stacks, 1 Sync.
- All four operator retry webhooks are actually false. Stored inline Action code exactly
matches reviewed source; Actions have startup/schedules/webhooks disabled. Native
check is live at daily 03:00 America/Los_Angeles; Global Auto Update's schedule
is disabled (reviewed source retains
skip_auto_update=true). Backup 01:00 and key rotation 06:00 are retained. - Configuration bootstrap only: no ProcedureRun/ActionRun, workload deployment, build,
workload source pull or restart. Core's normal Sync-cache fetch is not a workload pull.
Host
/opt/homelabremains0f522f0; #516's runner proposal on main is not applied there. Both pull timers are active, owned maintenance marker removed, no drain/pending relay intent. Core/Periphery retain 2026-10-25T11:33 starts; workflow isdisabled_manually.
Next owner action at that bootstrap capture (superseded): review the completion PR, then choose approval for the first scoped workflow deployment or optional image-publisher provisioning. First deployment/application smoke checks and unchanged retry remain pending. App/Core PAT are still unprovisioned; manual GitHub check remains available, but manual native/Action execution was not tested. Do not repeat bootstrap approval or automatically enable dispatch; no scheduled general deployment.
Historical input preparation — 2026-10-27 05:25:28 UTC¶
The owner-approved window 2026-10-27 05:24:58–05:25:28 UTC completed preparation
for oneuptime-incident-adapter, paperless-stirling-bridge, firefly, orders
and gh-pr-dashboard. Phase was then five_inputs_prepared_reddit_baseline_pending,
bootstrap_ready=false, not global input acceptance. See the
canonical preparation record.
- Native host-root render, private HMAC-state publication and
--checkall passed (exit 0) for the five profiles. All six rendered outputs, including Orders OAuth client JSON, were byte-identical to pre-existing files. Five private state directories are 0700, with 0600 keys/fingerprints and valid 1001:1001 ownership. - Before/after credential gates passed for all five scoped profiles: SOPS, canonical configuration and actual running environments matched. Orders OAuth also matched the actual pinned mounts in both containers. Fresh password-authenticated read-only PostgreSQL 15 adapter and PostgreSQL 18 Firefly sessions passed; roles were unexpired, matching password HBA rules were verified, and HBA file mtime preceded postmaster start. No HBA reload, credential rotation, SQL writes or network token validation occurred.
- Existing LiteLLM read-only check passed. Existing Reddit check failed stale before preparation and was still failed at that capture, not repaired or folded into the scoped credential pass. Both protected profiles' env/key/fingerprint bytes and metadata were unchanged.
- Root read-only HMAC RCA proved the stored Reddit fingerprint exactly matches
current source HEAD
3f0b0c9fd6b520fc8c12e3f73b1e0b93aaf42e46with pre-#514 Compose; all other components were unchanged and current source was clean. Only #514's gallery-worker read-only library Compose change makes the baseline stale, not credential drift. Same-key refresh without env rendering or rotation had not yet been approved or performed at that capture; the completed reconciliation is recorded above. - Host
/opt/homelabindependently advanced8bf7aa5→0f522f0during preflight (#514 Reddit and #515 Phoenix retained). The645bc3c-pinned checkout helper refused unexpected HEAD before fetch/merge; no operator host-checkout write or rollback occurred. The five profiles' input code/configs were byte-identical across645bc3c..0f522f0; preparation used current0f522f0, preserving four dirty tracked files. - Initial custom-auditor compatibility errors (age key
root:docker 0440, legal YAML standalone age-list dash, PostgreSQLinet::text/CIDR handling) were corrected only in an ignored operator artifact. No host permissions changed; those false checker failures were not runtime failures. - Application and Core/Periphery container IDs/start times stayed unchanged through
the window; no affected container was unhealthy and all healthcheck targets were
healthy. Both pull timers were briefly paused under owned maintenance, then restored
active and the marker removed. The runner was never drained/restarted; no relay
pending intent remained. Workflow stayed disabled; no Sync, grants, deployment
or Core change occurred. Private
input-preparation-result-20261027.json, window receipt and before/after credential-gate evidence remain in the existing root-private bootstrap directory. This preparation preceded the separately authorized bootstrap completion above; it did not itself authorize Sync, grants or deployment.
Historical bootstrap guard — 2026-10-26 20:24 UTC¶
PR #513 merged at 20:20:07 UTC
as 645bc3c5467f82ef749048ef296b457a48476e39; all 26 checks passed. Native pending
cache naturally refreshed to 645bc3c at 20:24 UTC, remote-content SHA-256
2551c36fe2b846ab5123e0c52f1a6823cc36f2c6ac3bbeaa2b84c0ca8e8b1c50. The preview had
31 changes: 18 creates (16 Procedures, 2 Actions), 13 updates (6 Stacks, 3 Repos,
4 Procedures), with no deletes, deploy targets, errors, Variable or UserGroup changes.
All four new retries (deploy-litellm, deploy-oneuptime-incident-adapter,
deploy-paperless-stirling-bridge, deploy-firefly) had webhook_enabled=false in
merged source and PENDING preview, not live-created Procedures at that capture.
Sync was then unexecuted; six legacy scoped webhooks were unchanged and Sync's enabled webhook
means workflow disablement is not a global lock.
At that capture, phase was runner_qualified_bootstrap_pending, host was 8bf7aa5
and normal Ansible apply was left alone. Source-only Inputs.snapshot(rendered=False)
passed all six profiles without SOPS decryption/output writes, and LiteLLM/Reddit
checks both exited 0; that was not DB-authentication proof or the later Reddit
result. Five-profile preparation approval was then pending; the
historical approval scope
is superseded by the completed preparation above. The unsafe 19:25 UTC #512
c9a0fd7 preview (SHA-256 prefix a1b2d997, managed=false, delete=false) omitted
four retry flags and inherited v2.3.4's true default; it is not approval to execute.
Guard validation recorded 93 focused and 275 combined tests, resource validation
and inline Action agreement. At that capture, live bot access was 36 Read + 2 Execute,
with no user-specific/base permissions, admin or Server/Build creation rights; no grants
changed during that guard review.
Credential-access follow-up — scoped acceptance complete, 2026-10-26 UTC¶
The post-merge checkpoint
records deployment
run 40299611768
failing at credential loading before host fast-forward, ResourceSync, Procedure
execution or new pending intent. At that capture, the legacy file
/etc/homelab/komodo-deploy.env was correctly 1001:1001 0600. The actual LAN
container ran as 1001:1001 plus GID 10002, not host Docker GID 987. During active
Ansible apply, /etc/homelab changed from inaccessible root:docker 0750 to readable
root:root 0755. The common ansible-pull/monitoring-deadman and operator-tools/labctl
roles disagree about that shared parent; a one-time successful read concealed it.
The follow-up removes the Komodo dependency on that parent rather than changing
the shared roles or adding Docker membership. Those policies and labctl traversal
remain an unresolved separate issue; /etc/homelab:ro is retained for labctl.
The new source contract is:
- API/default file
/etc/komodo-deploy/komodo-deploy.env,1001:1001 0600, withroot:1001 0710parent: group search-only, no listing/write. Renderer validation covers root ownership, exact group/mode, no extended/default ACL and no ancestor symlinks even for--check, validation and unchanged renders; unsafe directories are rejected, not repaired. - Enabled LAN preflight and actual runner use the same-path read-only directory
--mount. Missing source fails; a directory bind sees atomic replacement, unlike a file bind that pins the old inode. No new Docker socket or supplementary Docker group is introduced. - Root-only explicit
--migrate-legacyverifies the existing legacy pair equals reviewed SOPS before creating/rendering the new path. A different new credential or unsafe existing directory is refused. The old file, ciphertext and age identity remain untouched; no rotation or recapture. CLI status is 0 unchanged/valid, 3 changed/would change, 1 failure;--checkmakes no filesystem mutations.
The prior
approved-cutover checkpoint
(2026-10-26 UTC) recorded /opt/homelab fast-forwarded to 9240794, retaining #509.
The existing credential was migrated unchanged to the dedicated path with the
metadata above verified; the legacy file
was retained. Temporary runner-identity read-only preflight passed, not actual
recreated-runner acceptance. Fresh recheck found the deployment workflow disabled,
homelab-lan-01 offline with busy=false, its container absent and drain marker
present, and no relay pending intent. No ResourceSync, deployment or Core restart
was performed.
At that checkpoint, Ansible timers were active and the owned maintenance marker
was removed. Apply finished successfully at 10:51 UTC; ansible-pull-check
recorded an older exit 2 from 05:48 UTC, before cutover. These are distinct
observations, not a claim that all convergence checks passed.
The subsequent waiting checkpoint recorded the 11:22 UTC normal apply still active after a 180-second wait (exit 75: waiting, not failure), without a new maintenance window or runner start. That is historical, not the current operating state.
Historical activation checkpoint — 2026-10-26 12:06 UTC,
runner_activated_pending_convergence: the owner merged
PR #510 at 2026-10-26 11:27:39 UTC
as 0416d8e; all 26 PR-head checks succeeded. Review and controlled activation
are complete. Normal apply finished 12:01:20 UTC, exit 0, without interruption;
the 11:49 UTC check-service exit 0 may be a lock skip, not full convergence proof.
The locked host fast-forward to 0416d8e preserved local edits/paths. Credential-only
common and allowlisted runner-only applies succeeded; runner 24873 was online/idle.
Actual default-identity, directory-mount, SOPS consistency and read-only API checks
passed, but only /etc/homelab as root:root 0755 was observed at that checkpoint.
Both-state and natural-convergence acceptance were then open. See the
historical activation record.
Completed acceptance — runner_qualified_bootstrap_pending, 19:05 UTC: source,
main and host were 0416d8e at that capture, not the later bootstrap preflight.
The bounded read-only observer watched the next natural
apply 18:54:43–19:04:57 UTC, exit 0, full recap
ok=439 changed=29 skipped=98 failed=0. The same actual container passed credential,
SOPS, default-identity/mount and read-only API probes before, during the naturally
restrictive root:docker 0750 state and after return to root:root 0755.
Credential/both-mode/natural-convergence acceptance is complete, without chmod,
forced apply, timer pause, markers, restart or credential changes. The separate
17:50–17:51:58 UTC check-mode run failed (exit 2) at
operator-shell : Set operator shell home with an undefined value
(ok=135 changed=3 failed=1 skipped=76); it is not fixed and this is not whole-host
check-mode acceptance. See runner evidence
and the canonical acceptance record.
The reviewed flags remain infra-services-only, with role defaults false. At the 2026-10-26 acceptance capture, both timers were active; maintenance/drain markers and relay pending intent were absent. Deployment stayed disabled; no ResourceSync, grant change, deployment or Core/Periphery restart occurred in that window. Preserve unrelated host files and relay lock/pending recovery state.
Consumer rollback keeps the new mount and explicitly sets the existing
KOMODO_DEPLOY_ENV_FILE=/etc/komodo-deploy/komodo-deploy.env. Restoring old mounts
restores the bug; keep dispatch paused. Legacy cleanup requires separate explicit
approval, never automatic migration cleanup. The
rollout and bootstrap procedure
remains authoritative. External Sync/grants subsequently completed under separate
approval; optional image-publisher App/Core PAT and workload deployment remain gated.
Runner acceptance alone authorizes none of them.
Image-update extension — source merged, bootstrap complete 2026-10-27¶
The extension was source-only on 2026-10-26; its resources, schedules and relay grants are now live as recorded above. Optional credentials and execution validation remain pending. The initial runtime table below remains dated history. The image-update runbook is the operating contract.
- Daily 03:00
America/Los_Angelesnow belongs tocheck-image-updates, invoking its inline Action for 14 fixed native Stack checks. It checks same-tag digests only; pinned/local-build images are explicitly unsupported and incomplete accounting fails. Native false is not detected or unknown, never proof of current versions. LegacyGlobal Auto Updateremains manual,schedule_enabled = false, withskip_auto_update = true. No Procedure enablesauto_updates = true. - Manual
check-image-versionsand confirmedpropose-image-pin-updatesinvokerequest-image-pin-updates, dispatching GitHubcheck/propose. Weekly Dependabot is unchanged. The bounded registry policy covers 13 image fields in six stacks, across edge (Traefik/Homepage/AdGuard), documents (Paperless/Stirling) and finance (Firefly). Same-minor patches preserve prefix/suffix; major aliases remain same-tag digest-only with no semantic patch guarantee. OCI index/manifest digests are verified. Errors/private registries/local builds/digest-only/unsupported/truncated results fail closed; they cannot become false up-to-date reports or partial proposals. - The writer can change only allowlisted Compose image fields, literal Quick Reference Image table refs and future proposal history. Arbitrary documentation is never rewritten; warnings need human review. No automatic Rexit, OneUptime, Komodo or local-build/source pin updates. Native Rexit metadata checking is a separate, safe-unknown-capable path, not private-registry version-writing authority.
- Publishing creates a draft PR using a dedicated repository-only GitHub App
(Contents:write + Pull requests:write), with no merge, deployment, force operation
or
GITHUB_TOKENfallback. Requiredvars.IMAGE_UPDATES_APP_CLIENT_IDandsecrets.IMAGE_UPDATES_APP_PRIVATE_KEYare not provisioned. App-created PRs must trigger normal CI; the App's permissions are broader than the code's path allowlist. - The
.tsbodies are generated intoresources.tomlinline byscripts/render-komodo-actions.py;--checkverifies agreement. Core has no code-checkout mount and must never execute runner-writable file paths. The optional Core-only env is/etc/homelab/komodo-core/image-updates.env(root-owned parent 0700/file 0600), rendered byscripts/render-komodo-image-update-env.pyfrom future SOPS ciphertext. Only an example exists. The fine-grained dispatch PAT needs Actions:write on this repo, not Contents:write; token authority exceeds one workflow, constrained by a fixed code whitelist. No new token was created. Controlled Core recreation is required to load it; manual GitHub check needs no Core token or App. - Correlation uses workflow input
request_idand exact run nameKomodo image updates / op / cohort / uuid. The Action generates a UUID, dispatches once and polls the exact run for at most 35 minutes. Unknown outcomes require reconciliation, never blind retry or a latest-run success inference. - The existing result-aware workflow permits four roots: default
deploy-infraanddeploy-reviewed-images-{edge,documents,finance}. Cohorts explicitly pullhomelabthen conditionally reconcile their exact whole changed stacks from current main, including non-image changes. Review all relevant diffs and drain independent writers; do not bypass the workflow through direct UI execution.
The 2026-10-26 baseline was 38 grants (36 Read + 2 Execute) over ten live
Procedures and no Actions. Bootstrap added sixteen Procedures and two Actions;
2026-10-27 07:16:31 UTC bot readback verified 56 effective grants = 51 Read +
5 Execute over 26 Procedures, 6 Repos, 21 Stacks, 2 Actions and 1 Sync. Exactly 18
per-resource grants were added; the original 38 stayed unchanged. Execute covers only
Sync infra-services-01, deploy-infra and deploy-reviewed-images-{edge,documents,finance};
all others are Read. Operator check/propose Execute is not required by the bot. Native administrative delegation still makes
reviewed Git/Sync/Procedure/Action definitions a trust boundary.
Earlier public-registry smoke checks for Traefik, Homepage and Stirling were
metadata only, without image pulls, pin writes or PR publishing. On 2026-10-26
UTC the manual GitHub edge check
run 40299854748
succeeded on merged main (UUID 3a3c855b-381b-4787-b625-3c6fb6ec22ab). It reported
Traefik v3.7 → v3.7.16 and initial digest-pin proposals for unchanged Homepage
v1.13.2, AdGuard v0.107.79 and Unbound 1.22.0; publish was skipped and the
changed-file list was empty. No pins, PR, image layers, Sync or deployment resulted.
This is not all-cohort or Komodo Action-dispatch acceptance. PR #506 integration/CI
and the later #508 migration checkpoint are recorded in the journal. #510 review,
controlled runner activation and scoped credential/both-mode/natural-convergence
acceptance are complete, as are the subsequent 2026-10-27 bootstrap/grants. Optional
credentials/Core maintenance, native/Action execution validation, proposal publishing
and deployment/application acceptance remain pending.
Evidence and scope¶
- Original audit working branch:
feat/nocturne-result-filters, starting at4e7a8d1. - Production checkout and remote-main snapshot at the initial audit:
fec3dbc720289c7371eeb6654b1f6f4dc9817241. That production snapshot was ahead of the original working branch. Findings below were reconciled against it and selected live database fields, not inferred from older local declarations alone. - Later packaging baseline: remote main
7e7f79b6, with manual-only dispatch and newer operational corrections. The fresh isolated clone preserves those contracts; the original dirty working tree remains untouched. See the packaging record. - Capture: October 25 in
America/Los_Angeles(October 26 UTC). - Inspected container metadata, selected resource/schedule fields, and execution outcomes on infra-services. Database authentication stayed inside the container; secret values and deployment logs were not exported.
- Compared behavior with upstream v2.3.4 source. LXC versions were not measured during the initial capture; a later read-only check confirmed both at 2.3.4 (see version evidence). Image-update availability and long-term load were not measured.
Observed runtime¶
This table is the initial fec3dbc capture, before source uplift and guided
preparation; it is not a fresh observation of the packaging checkout.
| Area | Observation |
|---|---|
| Core / infra-services Periphery | Both v2.3.4; Compose still uses floating :2 tags |
| Core image digest | sha256:91b2afd7b3bb2abea0ebd3046d65ec457eb1f3d505868d1d60cb0f8562490ef9 |
| Periphery image digest | sha256:fb4b52961fcce7a31e3776930e422967b6640f653de876f59f26dea243e29beb |
| Resources | 3 Servers, 6 Repos, 21 Stacks, 10 live Procedures, 1 ResourceSync, 0 Actions, 0 Builds, 1 Builder |
| Git-declared policy | 19 auto-deploy, 1 self-managed (Komodo), 1 manual-upgrade (OneUptime); 7 declared Procedures |
| Native tracked dependencies | Reddit Explorer tracks .deploy-inputs/fingerprint with requires = "Redeploy"; other 20 Stacks have empty config_files |
| Image automation | All 21 Stacks have poll_for_updates = false and auto_update = false |
| Sync | infra-services-01 links to Repo homelab; resource path services/komodo/resources.toml; managed = false, delete = false |
| Deployment schedule | deploy-infra has an empty schedule; no Actions exist |
| Bootstrap schedules | Database backup daily 01:00, Global Auto Update 03:00, server-key rotation 06:00; enabled, inheriting Core timezone |
| Sample memory | Core ~90 MiB, Periphery ~106 MiB, Mongo ~158 MiB; one docker stats --no-stream sample, not a capacity study |
| Recent outcomes | Five sampled deploy-infra Updates completed successfully in ~38–256 seconds; corresponding Syncs took ~1.4–11.7 seconds |
At the initial capture the three bootstrap Procedures were live but absent from
that revision's resources.toml. Their
presence is not inherently drift: initialization resources are enabled. However,
their ownership, restore behavior and schedules deserve explicit documentation.
Global Auto Update is not a Git reconciliation fallback, and the observed Stack
flags do not permit it to auto-update those stacks.
Probe limitation¶
periphery --version reports its version normally. Do not use core --version
as a read-only version probe: this build starts Core instead. The probe printed
v2.3.4, attempted startup and exited because port 9120 was already occupied. Subsequent
container metadata showed the original Core and Periphery still running with unchanged
start times and zero restarts. No deployment was requested; startup-side database
activity from that short-lived process was not independently audited. Future checks
should use existing startup/version metadata rather than launch another Core.
What is already working well¶
- Git-authoritative ResourceSync, file-on-host stacks, and shared checkouts avoid duplicating the monorepo per stack and preserve Ansible/SOPS ownership of secrets.
- Komodo manages three Docker hosts without taking ownership of Saltbox containers or inventory-only customer applications.
- Native OIDC, PKI, explicit deployment policy and a self-managed control plane are
appropriate boundaries. Do not make Komodo restart itself through
deploy-infra. - OneUptime's explicit maintenance/migration gate is intentional, not an automation gap.
- Reddit Explorer already demonstrates the right efficiency pattern in production:
scoped desired-input fingerprint, native
config_files,DeployStackIfChanged, and a separate unconditional retry Procedure. Its source pull now runs after the ordinary infrastructure batch, not before it. - The captured
fec3dbcworkflow excluded**/*.md,reviews/**and roottests/**from push triggers, but mixed runtime changes could still trigger it. This is historical: newer main7e7f79b6is manual dispatch only, preserved by the uplift; no push exclusions are needed for the current policy. - The existing validator and Reddit mutation tests are useful guardrails. Extend them; do not replace them with a generic schema-only check.
Execution model at the audit capture¶
flowchart TD
A[Eligible merge to main] --> B[LAN Actions runner and policy checks]
B --> C[Safe host checkout fast-forward]
C --> D[Request ResourceSync]
D --> E[Fixed settling delay - default 15 seconds]
E --> F[Request deploy-infra]
F --> G[Pull three homelab checkouts]
G --> H[Conditional batch - 15 stacks]
H --> I[Pull Reddit source and publish fingerprint]
I --> J[Deploy Reddit only if inputs changed]
J --> K[Pull and unconditionally deploy Orders]
K --> L[Unconditionally deploy Firefly]
L --> M[Pull and unconditionally deploy PR Dashboard]
N[Five-minute resource refresh] --> O[Refresh cached state and pending changes]
The five-minute refresh does not execute Sync, pull the host checkout, or run
deploy-infra. There is no observed autonomous Git-deployment fallback schedule.
HTTP 200 from either webhook acknowledges the request, not successful completion.
The 15-second delay is not a dependency barrier. The sampled successful Syncs fit
within it, but that does not establish a guarantee.
Stages run sequentially; executions within a stage, including expanded batch executions, run concurrently. A failure stops subsequent stages. A long allowlist is therefore neither a serial deployment queue nor a host-isolation boundary.
Original prioritized findings and acceptance criteria¶
1. High: cover real deployment inputs¶
Gap: litellm, oneuptime-incident-adapter and paperless-stirling-bridge
are in the conditional batch and build local application content, but lack tracked
build-input dependencies. Code, Dockerfile, dependency, hook, and configuration-only
changes can therefore be missed by Compose-file change detection. Other stacks
also need an input inventory; changing a bind-mounted file is not necessarily enough
for its application to reload it.
Use native capabilities: add config_files with an explicit requires action
for suitable non-secret files. Use the existing Reddit fingerprint pattern for
aggregated source/build inputs or secret-bearing runtime inputs. Files belong in
config_files, not Compose file_paths.
In v2.3.4, tracked paths are literal UTF-8 files, not globs or recursively hashed
directories. The default action is None; explicitly choose Redeploy or Restart.
A restart-only action does not replace a deployment that must run pre_deploy.
Render inputs and publish the desired-input fingerprint before conditional
execution. A skipped deployment never runs pre_deploy, and tracked files are read
before that hook. Use git rev-parse HEAD for external checkout revision markers;
.git/HEAD normally contains an unchanged symbolic reference.
Do not track plaintext .env/compose.env: tracked content enters Komodo's
cache/display/diff machinery. Reddit's existing implementation uses keyed HMAC,
private state and atomic output, and verifies freshness in predeploy. Reuse those
properties rather than creating ad hoc unkeyed secret hashes. A source commit alone
also requires a clean/build-context policy for untracked inputs.
Acceptance: source-only, dependency-only, config-only and secret-only changes select the intended stack; unrelated changes and a second unchanged run do not; missing/stale inputs fail closed; no plaintext secrets appear in Komodo diffs.
2. High: make the relay wait for results, not elapsed time¶
Gap at the initial fec3dbc capture: scripts/trigger-komodo-deploy.py sent
signed Sync and Procedure webhooks with a fixed sleep between them and never checked
the terminal Update. That revision's if: always() summary claimed Sync completion
even on failures. Newer main corrected summary/dispatch policy; the uplift preserves
those corrections and replaces the remaining acknowledgement-only relay. Workflow
concurrency alone cannot cover a Procedure still running after the relay exits.
Recommendation: use Komodo's native API with a narrowly authorized automation
identity to execute RunSync, capture that execution's Update ID, and wait for
status = "Complete" and success = true before requesting RunProcedure.
Wait for that exact Procedure Update too. Fail on failure or a bounded deadline;
timeout means outcome unknown, not cancellation. Do not poll a generic "latest"
Update that might belong to a different operator.
The v2.3.4 raw response identifies persisted Updates as _id.$oid; GetUpdate
reads them. Single RunSync and RunProcedure executions provide correlation.
Do not substitute a direct batch/if-changed call and assume it has the same parent
Update contract. Retain Sync outside the Procedure whose definition it manages.
This needs a SOPS-managed API credential and permission review; do not widen the runner with a Docker socket or expose Core publicly. Until implemented, the runbook's manual terminal-Update verification remains required.
Acceptance: a Sync exceeding 15 seconds delays deployment; a failed Sync prevents it; failed/unfinished deploys cannot produce a success summary; overlapping dispatches are handled deliberately; no secrets enter job output.
3. Medium: extend selective application deployment¶
Gap: Orders and PR Dashboard still receive unconditional DeployStack calls on
general deploys. Orders explicitly builds with --pull and forces container
recreation. Reddit is no longer an example of this problem. Firefly's unconditional
execution is a deliberate environment-render workaround rather than an unconditional build.
Recommendation: adapt Reddit's desired-input contract to Orders and PR Dashboard.
First reduce when Orders executes; preserve its required build/recreate behavior
until separately proven unnecessary. Give base-image refresh an explicit policy
rather than relying on unrelated merges to trigger --pull.
For Firefly, track the encrypted/rendering inputs and preserve render-before-up ordering before removing its unconditional refresh. Keep force-deploy/recovery paths. Do not call a desired-input fingerprint a successful-release marker.
Acceptance: unrelated merges do not rebuild/recreate these applications; actual source/config/environment changes do; force retry and deliberate image refresh work.
4. Medium: isolate independent failure domains¶
Gap: failure to pull either LXC checkout still stops all later infrastructure deployment. Reddit or Orders failure can still prevent Firefly's environment refresh. Moving Reddit behind the ordinary batch fixed one dependency problem, not all of them.
Recommendation: move Firefly ahead of fragile application work, then consider host-scoped or application-scoped Procedures coordinated as independent chains. Keep pull-before-deploy and fail-closed behavior within each chain. Do not simply ignore failures or deploy an unavailable host's old checkout. Stage smaller batches if measurements show resource contention; do not parallelize every build by default.
Acceptance: failed SonarQube/Phoenix preparation does not block eligible infra-services work, and failed application builds do not suppress Firefly; no affected host deploys stale inputs.
5. Medium: use the installed version's capabilities deliberately¶
Gap: floating :2 tags obscure upgrade/rollback expectations, while the explicit
batch allowlist is still explained as necessary because tags do not work.
Observed: the infra-services pair is v2.3.4. Upstream introduced batch tags in v2.3.0 and fixed Procedure batch-tag filtering in v2.3.3. The old blanket statement is obsolete for this pair. Keeping explicit target lists remains a valid safety choice.
Recommendation: record/test matching exact versions or digests, including LXC
Peripheries. Then optionally replace duplicated allowlists with tested native tag
selection, preserving the three disjoint target sets: ordinary conditional batch,
special conditional application, and unconditional exceptions. Matching all
auto-deploy stacks indiscriminately would lose that distinction or deploy twice.
Do not remove homelab policy validation or enable automatic image deployment merely
to increase feature usage. COMPOSE_KOMODO_IMAGE_TAG currently does not control the
literal image tags in compose.yml.
Acceptance: reproducible upgrades/rollback and a target-selection test proving Komodo/OneUptime exclusions and exactly-once selection of all eligible stacks.
6. Medium: strengthen remaining validation and hook failure handling¶
Production's Reddit-specific checks are stronger than this older working branch's.
Nevertheless, in-memory production policy probes still accepted a disabled ordinary
batch stage, a nonexistent initial PullRepo target, and an extra BatchDeployStack
execution targeting Komodo. These are validator holes, not current live actions.
Require valid Server/Repo references, enabled required stages/executions, and a reviewed set of execution types. Preserve existing mutation tests and add negative fixtures for these cases. An upstream resource schema can supplement, not replace, policy checks.
The main homelab on_pull hook also joins into chown ... && test ... || chmod ....
A successful chmod can mask failed ownership repair. Group the guard as
chown ... && (test ... || chmod ...) and behavior-test both failures. The Reddit
hook already has stronger sequencing tests; this is not a reason to rewrite it.
Acceptance: each mutation is rejected; ownership or permission-repair failures remain failures. No production hooks were changed during this assessment.
7. Medium: make reconciliation and bootstrap ownership explicit¶
Document the three live bootstrap Procedures and linked-Repo Sync configuration alongside Git-managed resources. Keep Sync managed/write-back and deletion off unless ownership changes are explicitly reviewed.
After conditional input coverage and result-aware orchestration are established, decide whether a scheduled missed-trigger reconciliation is worth adding. Native Procedure/Action schedules exist, but do not schedule the current full pipeline every five minutes: it still contains unconditional work and a self-update boundary. A reconciler must perform external Sync → successful completion → deployment without syncing its own running definition, respect maintenance/upgrade gates, and avoid duplicate concurrent runs. Its schedule, identity and recovery path must be declared.
Acceptance: bootstrap from documented inputs reproduces the intended schedules; a missed trigger is either explicitly manual recovery or handled by a tested bounded reconciler, never assumed to be repaired by polling.
8. Lower: narrow environment exposure; tune sampling only with evidence¶
Both Core and Periphery receive the entire compose.env, including Core-only
settings and credentials. Separate service-specific environment inputs or use native
file-backed secrets where appropriate, retaining Ansible/SOPS as owner. Review terminal
access and automation permissions. Periphery already has the Docker socket, age key
and writable host paths; it is a privileged control plane, not a strong sandbox.
KOMODO_UI_WRITE_DISABLED disables UI editing; it is not an API write-denial
boundary. API authorization and sync ownership still matter.
The sampled ~354 MiB total memory does not justify replacing MongoDB or aggressively tuning monitoring intervals. Measure busy-period CPU, retained statistics and disk usage first. Core resource refresh, Core monitoring, Periphery sampling and image update polling are different mechanisms. Keep useful operator diagnostics without trying to duplicate the Prometheus/Grafana platform.
Retry semantics to preserve¶
For v2.3.4, a failed predeploy/build/Compose-up attempt does not generally advance the deployed-content baseline. Successful Compose up followed by failed postdeploy can advance it despite the overall failed Update. A subsequent unchanged conditional run may skip that failed verification. Preserve targeted force-deploy or verification retry paths and record application acceptance separately from Compose success.
The old blanket comment that any failed conditional attempt records a deployment hash is too broad for this version. Existing dedicated retry Procedures remain useful.
Documentation and ownership contract¶
Future implementation must update, together:
- Resource/Compose/relay configuration and focused regression tests.
- The Komodo service README and explicit-deployment runbook.
- Affected application READMEs, input/secret ordering, retry and rollback instructions.
- Agent conventions, journal and navigation; inventory/generators if ownership or host placement actually changes.
- This audit's statuses with separate implemented, deployed and verified evidence. A green webhook job or source diff is not rollout acceptance.
Keep PLAN.md as the historical master specification; use the live operating
references for as-built behavior. Do not rewrite old journal evidence as though
these improvements were present then. The July reliability audit now links here
for corrections to polling, Sync webhooks, UI/API permissions and self-sync advice.
Sources¶
Repository evidence below is pinned to the inspected production revision:
Upstream evidence: