Files
akyldash/docs/operations/CURRENT_STATE.md
codex 3d7df25d00
CI / tests-and-images (pull_request) Successful in 2m13s
Runner smoke test / runner-smoke (pull_request) Successful in 2s
fix Windows ACL checks to enumerate SIDs directly
2026-11-03 11:52:10 +03:00

31 KiB

Infrastructure current state

Windows ACL SID resolution — 2026-11-03

  • Read-only PowerShell 5.1 inspection found that display-name conversion of ACL entries can throw IdentityNotMappedException even though the ACL itself is readable. The installer and external-receiver guards now enumerate access rules directly as SecurityIdentifier values and read owners as SIDs; unknown principals remain visible to the existing fail-closed policy. Regression coverage exercises synthetic unmapped SIDs through the actual installer guard functions. These repository changes are pending review; no Windows ACL, installer, task, VPS, or production state was changed. The native PowerShell harness still requires execution on Windows before this behavior is confirmed in that runtime.

External deployment runtime binding — 2026-11-03

  • PR #59 is merged to main; it contains the root-only read-only status endpoint and exact-literal SSH dispatcher for status and deploy. The status path bounds the Gitea run list to ten entries, verifies the selected run/workflow/current main, validates artifact metadata and ZIP bytes, and returns only a release tuple. This unmerged change adds a two-SHA compatibility gate: the fixed root helper compares protected Compose/Caddy bytes and installed release policy with Gitea content pinned to the release SHA, verifying each Git blob hash and size. This permits application-only release B after bootstrap A without advancing the root checkout or executing new root code. A runtime config or policy change fails closed and requires a reviewed bootstrap update. The Windows response state is prepared, never candidate; the --once task stores a protected local tuple and cannot call deploy. A separate disabled auto-apply task and config gate require the tuple to match a fresh status response from a previous poll. Status and apply retain current-main, successful CI/release provenance, descriptor digest, external backup, cutover, canary, and rollback checks. The receiver repeats the config check before journaling/stopping services and again after backup immediately before switching refs.

  • Windows client предназначен для подготовки и auto-apply; обе задачи по умолчанию выключены, целевой путь фиксирован: D:\Dev\akyldash, SSH alias — akyldash_release. Примеры config и Task Scheduler XML также выключены; задачи запускаются от интерактивного пользователя с минимальными правами, не допускают параллельных запусков и не будят компьютер. Windows task, SSH key, VPS command, helper и trigger не устанавливались и не запускались. Путь canonical C:\ProgramData\Akyldash\release-trigger.json известен, но содержимое и enabled-состояние не читались. Метаданные Windows подтверждают C:\Python313\python.exe, действительную Authenticode-подпись Python Software Foundation и защищённые ACL интерпретатора.

  • Linux fixtures прошли для Python import и command-path poisoning, включая очистку унаследованных GIT_* и фиксацию путей ssh.exe/git.exe/powershell.exe. Installer получает ограниченный bundle из точной вершины main: упаковщик читает Git objects commit, tree и allowlist-файлов; elevated installer проверяет SHA-256 ZIP и цепочку commit → tree → файлы из одного read-locked потока до записи и не запускает Git. ACL проверяются явными масками мутаций и подмены; текущая учётная запись не может владеть кодовыми путями, а право создать соседнюю папку не считается подменой предка. Windows ACL/install проверки пока только прошли код-ревью; installer и launcher на Windows не запускались. Production CD остаётся выключенным.

  • Remaining owner prerequisites are installation of the reviewed root helper, exact forced-command/SSH host-key setup, a protected Windows client and its private state/config ACLs, private D: backup ACLs, and all previously listed backup/restore and production readiness gates. Neither the status endpoint nor client advances the host checkout or updates root helper code.

  • Read-only Gitea check for fresh main SHA f19faf628e67015febac22d0ec93ce4782caf4bf: CI run #706 succeeded; release run #708 failed at the explicit registry credential-presence check because REGISTRY_USERNAME and REGISTRY_PASSWORD are absent; handoff run #709 was skipped. No secret values were read. Image publication remains owner-blocked.

Owner decision recorded 2026-10-26: a complete backup must not be stored on the VPS, including temporarily. The local archive/full cold-backup commands in BACKUP.md are superseded and unauthorized. PR #59 now contains a proposed Windows-controlled external stream receiver and root-side deploy/rollback code. The Windows rerun passed 12 synthetic client checks and 8 protocol checks, plus a disposable real-NTFS fixture for the private owner ACL. The production D: path, SSH connection, VPS installation, full backup, and separate-target restore remain unverified. No verified external backup or separate-target restore exists, so production deployment remains blocked by the backup gate.

Code-only deployment work — 2026-10-31

  • Work is being prepared in the existing PR #59 branch from the then-current main; no merge, VPS operation, live release, or workflow dispatch was performed for this work.
  • The release descriptor now binds both the release run and its successful CI source run. Host-side source verification uses the installed gitea-api helper contract; the PR carries the inspected helper fixture plus a bounded binary-download patch. The installer verifies the fixture hash before applying the patch.
  • Backup bytes are streamed through a bounded SSH protocol to the fixed Windows destination. Root release state stores only a secret-free backup identity. The rollback handshake requires the exact previously pinned archive and creates a fresh external backup before image references change. Local VPS archive paths fail closed.
  • Review fixes in this PR require Gitea status=completed with conclusion=success, resolve main from /opt/akyldash/current, write a durable pending journal before stopping services, and reject an already applied release before downtime. The Windows client checks D: capacity, NTFS/path/ACL owner and allowed entries, and the local ignore rule before SSH; it requires 100 GiB plus 1 MiB free. The Windows rerun passed 12 synthetic client checks, 8 protocol checks, and a disposable real-NTFS private owner-ACL fixture; actual D:, SSH, and VPS remain unverified.
  • Receiver cleanup after a post-publication ACL failure is identity-bound to the owned staging directory and permits only the expected archive files. A replaced, reparse, or unexpected target is left for manual review without an ACK; this path is covered by local fixtures and the Windows rerun.
  • Synthetic tests cover archive/protocol integrity, helper download rules, artifact/run provenance, receiver publication, release schema, and rollback refusal. The 12 synthetic Windows client checks, 8 protocol checks, and disposable real-NTFS private owner-ACL fixture passed. The actual D: path, SSH alias, Gitea helper against the live instance, root helper installation, artifact redirect, production backup/restore, and actual release/rollback remain unverified.
  • Owner bootstrap blockers remain: verify a root-controlled SSH route for the fixed commands using existing host-key checking; provision only the necessary read-only Gitea token, exact redirect origin if needed, and approved RU/KY canaries; confirm registry pull and retention policy; test the Windows client and a separate-target restore. No new key, sudo rule, Docker access, credential, or VPS setting is added by the code change.

Read-only deployment recheck: 2026-10-25. Source: SSH as codex, direct-origin and public HTTPS probes, local main checkout, and Gitea API. No VPS files, services, firewall, containers, volumes, production data, or credentials were changed or inspected. Root-only Docker state, UFW rule details, protected file metadata, current backup/restore validity, and the live corpus/index remain unverified. The 2026-10-18 inventory below is a historical snapshot where it conflicts with this dated recheck.

Latest read-only recheck — 2026-10-25

  • https://akyldash.kg/review returned HTTP 200 with valid TLS through the public Cloudflare edge and through direct HTTPS to origin 206.223.249.118. This verifies that route only; it does not prove search or index readiness.
  • TCP listeners now include 22, 80, and 443. The older no-listener/Cloudflare 521 observations below are historical, not current state.
  • VPS reports 4 CPUs, 7.7 GiB RAM (about 3.7 GiB available), and 31 GiB free on /. Current runner-mount free space was not rechecked.
  • /opt/akyldash/current exists, is a root-owned directory mode 0755. Reading its Git SHA failed with Git's dubious ownership safety guard; no safe.directory setting was added, so the installed revision is unknown.
  • akyldash-act-runner.service is active under akyldash-runner; installed act_runner is 0.2.13. The separate akyldash-runner-restart.service remains failed since 2026-10-14; its cause could not be read from the unprivileged journal view. No restart was attempted.
  • akyldash-verify, akyldash-compose, and akyldash-rollback are absent from PATH in the unprivileged check; no root helper or Docker operation was run.
  • Gitea is 1.25.5; main is b5adc09203cbbbd298054248206b9f47ae4a44ca. CI run 175 and runner smoke 176 succeeded on that commit. Package write access and runner inventory remain outside this API principal's permissions.
  • PR #59 head 852d639c455ff1c60202405e112d47b8128d66c6 received formal review #308 with five requested fixes. CI run 177 failed in test_digest_rollback_fails_before_stopping_or_changing_references because the test's global Path.stat mock made Path.exists() report a false recovery-journal hit under CI Python 3.12; runner smoke 178 succeeded. A fix batch in this PR isolates state paths, handles ambiguous alias/cutover outcomes, verifies rollback image IDs, and adds a synthetic artifact upload check. On 2026-10-25 CI run 179 and runner smoke 180 succeeded at PR head 9d8ce6b6; CI took 2m43s and the synthetic upload step reported success. The run API identifies run ID 567 and reports run_attempt: 0, Gitea's legacy sentinel. The release workflow therefore uses the unique run ID and does not depend on the unavailable attempt value. The run's artifact link was downloaded from Gitea UI as artifact-smoke-567-: the ZIP was 191 bytes, SHA-256 ae0a2262147f222aacb4fdd1d12459f7160c67b1d0ee5d7ae2db1de8958f120f, and contained the expected 51-byte marker.txt. The API artifact listing available to this principal returned no entries, but the downloaded file verifies artifact availability. No live release or VPS change was made.
  • The release descriptor SHA-256 sidecar now records release-manifest.json relative to the release directory, matching the flattened layout observed for downloaded Gitea artifacts. A local synthetic regression verifies the checksum from the artifact extraction root; the credentialed release workflow remains unrun.
  • Owner-only outstanding evidence: sanitized container/volume and protected file metadata; current backup and separate-target restore verification; live corpus/index counts and RU/KY canaries; Cloudflare TLS mode; registry writer identity and digest retention. Never include secret values in that evidence packet. The checklist is in DEPLOYMENT_OWNER_PACKET.md.

The deployment PR adds a manual main-only image-publish workflow and an owner-run --no-build cutover path, but neither has been executed against the VPS. Its release descriptor is an unsigned trace record; cryptographic image attestation remains a separate owner decision.

Historical host snapshot — 2026-10-18

Source: SSH as codex, local repository inspection, and Gitea API. Root-only Docker state, UFW rule details, and protected file metadata were not verified.

Historical VPS facts — 2026-10-18

  • Host: avprogs.hlab.kz, 206.223.249.118, SSH port 22, user codex.
  • OS: Ubuntu Server 24.04.5 LTS; kernel 6.8.0-142-generic from the prior inventory audit.
  • Resources: 4 vCPU, 7.7 GiB RAM, 3.8 GiB swap, 99 GiB root filesystem with 84 GiB free on 2026-10-18. Runner storage is a separate 15 GiB ext4 loop mount with 12 GiB free at that check.
  • Network: interface ens3, IPv4 206.223.249.118/32.
  • codex is UID/GID 1000, groups codex and users; sudo -n true requires a password. Docker CLI cannot access /var/run/docker.sock as codex.
  • SSH service is running. The inspected sshd configuration has public-key and password authentication enabled and PermitRootLogin yes; no SSH changes were made.
  • Bootstrap completed. Docker CLI 29.9.0 and Compose 5.6.0 were rechecked over SSH on 2026-10-18; containerd 2.4.1 and runc 1.5.2 are from the prior package audit. The Docker daemon API remains root-only.
  • /etc/docker/daemon.json has the expected local log driver and 10m / 3 rotation limits. Bootstrap paths exist with root ownership: /opt/akyldash mode 0755, /etc/akyldash and /var/backups/akyldash mode 0700, and /var/lib/akyldash/data mode 0750.
  • /opt/akyldash/current, verifier/rollback binaries, and the sudoers file are absent. codex has not received sudo or Docker-group access. Metadata for /etc/akyldash/production.env and /etc/akyldash/review_secret could not be checked because access is denied. The target /var/lib/akyldash/data/minjust-normalized and its manifest metadata were also denied; their presence, ownership, and contents are unknown. No sudo was used.
  • TCP listeners observed: SSH 22 and loopback DNS only; no listener on 1500, 80, or 443. No ISPmanager package or service unit was found through the unprivileged checks; root can confirm with the audit script below.
  • Owner reports UFW is active and allows TCP 22, 80, 443, and 1500. Rule details were not independently read; no firewall changes were made.
  • Additional read-only runner checks on 2026-10-12 confirmed / is ext4 with about 92 GiB free at the prior audit, /usr/bin/dockerd-rootless.sh exists, and git.vakanaut.ru resolves to 195.209.48.28. From the VPS, https://git.vakanaut.ru/api/v1/version returned Gitea 1.25.5; runner registration and Gitea Actions protocol connectivity are not confirmed.
  • akyldash.kg resolved to Cloudflare IPv4/IPv6 edge addresses. HTTPS returned Cloudflare 521 on this historical 2026-10-18 check; current recheck is recorded above. Cloudflare Full (strict) mode remains unverified.

Historical application and Gitea facts — 2026-10-18

  • Repository: admin/akyldash, default branch main; Gitea version 1.25.5.
  • The repository API reports push permission, Actions and packages enabled, and PRs enabled. main is protected and requires one approval; the current API principal cannot push or merge directly to main.
  • The repository runner-list API returns 403 because this API principal is not the repository owner. The latest verified CI run 75 and smoke run 76 both succeeded on main head 0e276ae6. The Actions run-list endpoint returned 403 for this API principal during this recheck, so no newer run state was independently retrieved. The owner reports the runner is registered and Idle; repository runner inventory and package publishing permissions remain unverified.
  • No public application listener is observed on TCP 80/443. Docker container/volume state still requires the root audit. Repository Compose describes Caddy 2.11.6 pinned by digest, Python Search API 0.12.2 in this branch, SQLite account/review data, and OpenSearch 3.7.0 with analysis-icu.
  • The target publishes only Caddy on TCP 80/443. Backend and Caddy share a proxy network; OpenSearch and backend share a separate internal network, and backend has no host port. Caddy certificate storage, OpenSearch, and application data use persistent volumes/bind mounts.
  • The root-owned repository checkout, production env/secret files, Compose volumes/containers, backup, verifier/rollback helpers, and application are not confirmed installed. TLS and registry digest deployment for backend/OpenSearch are not configured.
  • The repository has a cold-backup archive/verification tool and a one-shot restore apply tool that requires a marked replacement host. Backup/restore tests use synthetic data; neither tool has run against production. The archive preflight permits a clean first deployment to skip backup, requires backup for existing persistent data even without containers, allows absent named volumes only in explicit corpus-only initial-deployment mode, and rejects inaccessible/ambiguous mounts and incomplete/running service containers. Initial deployment docs require a second verified backup after index and admin initialization.
  • Recovery bootstrap has an explicit --recovery-target mode that refuses the inventoried production hostname/IP; its host-identity guard has shell tests. The mode has not been run on a VPS.
  • The repository defines a root-owned read-only verifier, Compose config and status helper, guarded rollback helper, and installer with exact sudoers command rules. None was installed on the VPS at this inventory. The current PR adds fixed root-side release/rollback receivers without adding a codex sudoers rule or Docker access. Neither receiver has been installed or run.
  • The former local-VPS rollback path has been superseded and now fails closed. The PR rollback path requires a root-pinned external archive identity, rechecks its exact bytes through the owner client, creates a fresh external backup before changing refs, and preserves a secret-free recovery journal on failure. Synthetic tests only; no production rollback was performed.
  • The image release workflow now starts after successful CI for a push to main. It checks the source workflow/event/branch/conclusion and exact source SHA, checks out that SHA, and uses both release and source run IDs in the schema-v4 descriptor. Registry secrets REGISTRY_USERNAME and REGISTRY_PASSWORD remain unconfigured/unverified; no values were created or inspected. PR image builds remain no-push. The descriptor SHA-256 is included in the artifact name, and the artifact also retains its checksum sidecar. The descriptor remains unsigned trace metadata, not cryptographic attestation.
  • A separate deploy-handoff.yaml runs after a successful release and checks the source run and stale-main guards. Its job is explicitly NOT DEPLOYED - handoff status only; a green result is not a deployment. It reads artifact metadata through gitea-api only if that helper is available in the job image; otherwise it reports metadata and host handoff as unverified. It does not download artifact ZIPs, connect to SSH, or apply a release. No repository workflow binding to a Windows runtime starts the receiver. PR #59 now proposes a separate disabled local --once trigger that queries a root-verified tuple; it is not installed or active. Image release is automated, but production deployment is not yet automatic CD. The original audited gitea-api helper lacks binary output and safe 302 handling; the current PR carries a tested patch and host-side artifact verifier, but neither is installed. No deploy key, fixed SSH entry, extra sudo rule, or Gitea CD variable has been configured. Production apply remains blocked on owner bootstrap, external backup and separate-target restore, and the other gates above.
  • A repository-scoped official act_runner 0.2.13 installation is configured in infra/runner/: rootless Docker under akyldash-runner, 15 GiB ext4 loop-backed image/workspace storage, capacity 1, per-job limits (2 CPU, 3 GiB RAM, 512 processes), account slice limits (2.5 CPU, 4 GiB RAM, 2048 tasks), rotated file logs, and a systemd health timer. Jobs target the akyldash-ci label and do not receive either Docker socket. Latest verified main CI run 175 and smoke run 176 succeeded on b5adc092. On 2026-10-18, SSH showed akyldash-act-runner.service active under akyldash-runner, with zero restarts and about 115 MiB current memory. Its rootless Docker store is mounted from /dev/loop0 as ext4 with rw,nosuid,nodev; the 15 GiB mount had 12 GiB free. The separate akyldash-runner-restart.service remains failed with exit status 1 since 2026-10-14; its journal had no entries, so the cause is unknown. The latest verified main CI run 75 and smoke run 76 succeeded on head 0e276ae6. The Actions API returned 403 during this recheck, so no newer run state was independently retrieved. A read-only SSH recheck on 2026-10-21 confirmed the active akyldash-act-runner.service runs as akyldash-runner with ProtectHome=read-only; ReadWritePaths includes the runner runtime/config/log paths and /run/user/999. Its ExecStart uses /etc/akyldash-runner/config.yaml. The live unit retains a writable carveout for /run/user/999; the installer keeps that existing carveout and changes ProtectHome from yes to read-only so the runtime path remains visible. No VPS unit was changed. Main commit d2fb4b81 passed CI run 115 and smoke run 116. On head 3a89dc21, CI run 63 failed at the host audit root guard and smoke run 64 succeeded. On head 436806a8, CI run 65 failed in the Caddy container action with exec: "validate": executable file not found; Kaniko was skipped and smoke run 66 succeeded. On head d96fc4d3, CI run 67 failed because the Caddy action's fixed /github/workspace/... path did not exist; Kaniko was skipped and smoke run 68 succeeded. On head 5d72bb09, CI run 69 and smoke run 70 succeeded. CI validated the Caddyfile and built the backend and OpenSearch images using ${{ github.workspace }}. Actual per-action resource-limit enforcement and peak usage were not independently measured or verified. Image publishing, host-wide resource metrics, and production deployment remain unverified. No VPS changes are made.
  • An isolated local Compose smoke test built backend/OpenSearch images, reached healthy status for OpenSearch, backend, and Caddy, and returned HTTP 200 for /review through Caddy. Its containers, networks, and volumes were removed afterward. This does not verify VPS startup, DNS, or public TLS.
  • The app has no migration framework or SMTP setup. Repository versions on this branch are: backend 0.13.0, frontend 0.2.0, Telegram bot 0.2.2 (the bot is not deployed on this VPS).

Corpus readiness (2026-10-18)

  • The local raw archive and normalized corpus are ignored under data/; no corpus files were changed. Their read-only SQLite manifests both pass quick_check and each report 209,958 documents. The raw manifest is 28,295,168 bytes with SHA-256 f8e3dd2fdec607583ca09cfdb5b76171bb7bfa1e41a0e07ee5aca8481e18e17e; it has one fetch error, code 6, absent from documents. The normalized manifest is 31,571,968 bytes with SHA-256 1679c20e6d0f3e5a9b50737c006c4417a28ebcaf7467d194d3bf8243538f83bf; all entries are success, schema 1, normalizer 1.0.0.
  • The read-only full-file inventory in /tmp/akyldash-corpus-audit.Mjr6aa stopped before validation in run b4a4556e30cc464d90bc8d75ef1673e9 with Python int too large to convert to SQLite INTEGER. At stop it had processed 2,120,420 raw entries (1,081,428 files, 1,038,992 directories) and 397,856 normalized entries (246,321 files, 151,535 directories). This is a verifier metadata storage failure, not a corpus finding. A full rehash with the corrected, versioned verifier is required before readiness can be stated.
  • A fresh full rehash is running in /tmp/akyldash-corpus-audit-final.gtsD87vc, run f4dd5e53020f442bbbecee2d0e4986a6, with verifier SHA-256 83dac230099fc91972cd14e86e9f8ec72d13bca08f53d402f13a169e096d98f5. result.json currently reports scan_status: running; its live counters must be recorded after completion. Sources remain read-only.
  • Through the repository client, bounded MinJust OpenData requests for GetDocumentListByQuery, GetDocument(6), control GetDocument(1096), and CheckAvailable each returned HTTP 404. The list API returned no code list; the direct requests do not establish whether code 6 exists. The public listing page is reachable, but it does not map the numeric API code. A metadata-only CheckAvailable request from the VPS failed in HTTP/2 with curl exit 92 (INTERNAL_ERROR), HTTP status 000, and TLS verification 0; the HTTP/1.1 retry was not completed. Treat the code 6 disposition and full corpus readiness as unresolved.
  • On the VPS, read-only stat of /var/lib/akyldash/data/minjust-normalized and its manifest was denied. Production corpus presence and metadata remain unknown. No corpus was transferred and no production service, Docker socket, runner policy, or VPS configuration was changed.

Current MinJust API staging (2026-10-20)

  • The public site's current API uses POST /api/v1/GetDocuments with page size 12, GET /api/v1/GetDocument?DocumentCode=..., and GET /api/v1/GetEdition?editionId=...&lang=ru|kg. A bounded page-1 probe reported 210,639 filtered records; no full listing was enumerated for this recovery. Official alias 1-4 resolves to canonical document code 205064.
  • Document code 6 has 201 official editions. The API's lang=kg response places Kyrgyz HTML in contentRu while returning contentKg=null; staging maps by requested language (kg to archive ky) and preserves the edition order from the document response. Normalization and search export now retain and use that order instead of sorting IDs.
  • Without exact=true, one probe for edition 920292 returned response field id=920492 despite a different text length/hash. Two bounded exact=true probes returned matching IDs and names/dates from GetDocument: edition 920292 / 04.08.1998 (485,487 RU characters, SHA-256 2b51c2ff4e9b91cbe2a7ed052a5a9ed9150bda993fc15c120ac948580926b718) and edition 920492 / 13.04.2017 (1,139,908 RU characters, SHA-256 e5f0fdbb47766e5a5dbb971183d765e4c1dcaf9c969a218a84206244ce9132e8). Every edition request now uses exact=true, requires returned ID equality, and checks RU/KY names against GetDocument metadata.
  • backend/ingestion/minjust_current.py stages one document into a separate generation, at no more than one request per second, with a 16 MiB response limit, a 4 GiB text budget per document, contract-bound checkpoints, and atomic publication. Staging requires a private 0700 directory owned by the current user and rejects symlinks under checkpoints/document paths; legacy corpus exclusions are anchored to the repository root. The corpus verifier continues to validate unchanged normalized manifests from versions 1.0.0 and 1.1.0. The earlier non-exact pass is quarantined at /tmp/akyldash-current-code6.ZAMC37u4ex/qualification.json: its 240 language responses (304,493,520 bytes) remain unverified and are not reusable. The retained work-tree SHA-256 is 1e874c1d7fab1e00e7e45ff1b16422ea474e22ee13c901ac9a0612f47aa5d24a.
  • Exact-only code-6 staging is complete at /tmp/akyldash-current-code6-exact.L4k3AvnOlP: 201 editions and 402 complete language checkpoints, 604,377,885 raw bytes. The raw manifest passes quick_check; its document checksum matches the staged directory at 81d8e1a53b2ebef61f08b56d04b3f0a80077d587a4fadae0152bb4b7f13055ea. Separate normalization at /tmp/akyldash-current-code6-exact.L4k3AvnOlP-normalized contains 201 editions in official response order, passes quick_check, and records normalizer 1.1.0 (2,048,797,024 bytes). The prior data/minjust-cbd and data/minjust-normalized trees and production were not used as input or modified. The raw staging manifest still records attachments and embedded media as unverified; this single document does not establish corpus completeness or production readiness.

Checks not completed

  • Docker Engine version from the server API, container and volume inventory, protected env/secret metadata, sudoers, and detailed UFW rules require root. infra/bootstrap/audit-host.sh is provided for an owner-run read-only audit.
  • The backup archive/restore tool was tested with synthetic local data; a backup and restore of real production data remain unverified because production is not deployed.
  • Cloudflare edge response was checked; origin DNS target, origin certificate, Full (strict), registry publishing, host-wide runner metrics, and production deployment have not been verified. The owner-reported Idle state has not been independently checked through runner inventory because the API returned 403.
  • This audit made no host changes. No package or service was installed by this audit; the owner performed the initial bootstrap beforehand.

Next owner actions

  1. Install the approved root-owned checkout after its PR is reviewed, then run sudo bash /opt/akyldash/current/infra/bootstrap/audit-host.sh and share the output. The script prints no secret values and does not mutate the host.
  2. After reviewing the audit output and confirming the target files are absent, run sudo bash /opt/akyldash/current/infra/deploy/prepare-production-config.sh; it refuses to overwrite existing files. Set the approved DATA_ROOT and place the approved corpus/index and Cloudflare origin configuration. Do not deploy until data/service state and the required backup path are reviewed.
  3. Run sudo bash /opt/akyldash/current/infra/deploy/install-helper.sh after reviewing the checkout; it grants only fixed verification, Compose config/status, and guarded rollback commands.
  4. After merging the reviewed PR, confirm a subsequent CI and smoke run on akyldash-ci. Image publishing and production deployment remain manual and are not part of CI.
  5. Test backup/restore and rollback only with synthetic data in an isolated environment before any production deployment.

Change in this PR

Adds a read-only, resumable full-file corpus inventory and normalized-data cross-check, a source/staging comparator requiring nonzero documents and fragments plus held shared artifact locks, synthetic regression tests, and an operator handoff runbook. CI's existing infra/deploy unittest discovery includes the new tests. No workflow, runner, VPS, production data, or Docker settings are changed. The first real corpus scan stopped on an integer-storage bug; full validation with the corrected verifier is still required. The source API's code 6 outcome remains unconfirmed.