Koder Hub — Publication Coverage Registry

Tracks which Koder Stack components are reachable via the Hub publication API GET https://hub.koder.dev/api/v1/apps/<slug>. Source of registered components: registries/component-names.md.

The audit probes three name variants per row of the source registry (type ∈ {product, service, engine}meta and umbrella types are non-publishable and excluded):

  1. <slug> — the canonical registry slug
  2. <aliases[0]> — the first coloquial alias, if any
  3. koder-<aliases[0]> — the registry-noncompliant compound form

A row passes when (1) returns HTTP 200. The other variants are auxiliary: a 200 on (2) or (3) without (1) means the package was published under a non-canonical name and should be re-published under <slug> (the registry is authoritative; release engineering should align with it).

Status values

Value Meaning
OK_CANONICAL Published as <slug> (registry-aligned)
DRIFT_AS:name Published only as <name>, where <name> ≠ <slug>
DUAL Both <slug> and a non-canonical variant return 200
MISSING None of the probed variants exist (expected for components that have not shipped a first release)
ERR Probe error (rate-limit retry exhausted or network)

Last audit: 2026-06-03 (Kodix release spot probe)

Spot probe 2026-06-03 (kodix#063 release v0.18.0): GET https://hub.koder.dev/api/v1/apps/kodix → HTTP 200 (canonical slug). Old GET .../apps/koder-distro → HTTP 404 (rebrand #054 — old slug gone, not dual). Classified OK_CANONICAL for kodix. ISO download itself is served from kodix.koder.dev/releases/latest (jet static), not the Hub apps API; the Hub row tracks the catalog entry only.

Previous audit: 2026-05-20 (Kruze + Box spot probes)

Spot probe 2026-05-20: GET https://hub.koder.dev/api/v1/apps/koder-kruze → HTTP 200, slug=koder-kruze, version 1.0.20 (Flow has v1.0.23). Aliased kruze → HTTP 404. Classified OK_CANONICAL; row added under that section. Hub-side version drift tracked in products/horizontal/kruze/app/backlog/pending/157. Kruze had been absent from component-names.md until this commit landed the row.

Spot probe 2026-05-20: GET https://hub.koder.dev/api/v1/apps/koder-box → HTTP 404; kbox alias → HTTP 404. Classified MISSING; row added below. Follow-up to publish tracked in infra/net/kbox/backlog/pending/107.

Previous audit: 2026-05-14 (post #119 data fix)

Probed via curl -sS https://hub.koder.dev/api/v1/apps/<name> with 0.3s between requests + exponential backoff on 429.

Total publishable components (product+service+engine from component-names.md): 115

State Count
OK_CANONICAL 25
DRIFT_AS 0
DUAL 0
MISSING 89

#119 data fix — completed 2026-05-14

All 5 non-aligned publications resolved. The 4 DRIFT cases were renamed via PATCH /api/v1/publish/<old-slug>/rename (#107 primitive: atomically updates apps.slug to canonical AND records old_slug in slug_reservations AND prepends to previous_slugs). Old URLs continue resolving to the canonical record by app_id — verified post-rename: GET /apps/<old-slug> and GET /apps/<canonical-slug> return the same id. The DUAL case (kmail + koder-mail) was deduplicated by archiving the kmail record (soft delete; hard delete is gated by KODER_HUB_DEV_DESTRUCTIVE=1 and not enabled in production — acceptable since kmail v1.0.0 was a placeholder seed with no real downloads).

Registry slug Old slug App ID Action Pre-state Post-state
koder-term kterm a4c343cd1830fa2e24724c997eb36112 rename kterm 200 / koder-term 404 both 200, same id
koder-icon kicon a181b8f67ddcab31e9fb0690e7649c60 rename kicon 200 / koder-icon 404 both 200, same id
koder-pkg koder-kpkg 4dc5ee97514ee0207a95597f5c011a33 rename koder-kpkg 200 / koder-pkg 404 both 200, same id
koder-termux koder-ktermux a118be2aaa6f80a58c933f1d3f5b2a67 rename koder-ktermux 200 / koder-termux 404 both 200, same id
koder-mail kmail (id 0583031114687b1746a1e1eac7ae48c0) cdfa21dddf5546d0bc9b31ce31260c16 archive kmail both 200 (different ids) kmail 404, koder-mail 200

Bonus alias-resolution behavior

After rename, GET /api/v1/apps/<old-slug> returns HTTP 200 with the canonical record in the body (not 301 as the handler intends via ResolveSlughttp.Redirect path). Cause: kdbnext keeps the stale idxAppBySlug secondary index entry pointing the old slug at the app row, so GetAppBySlug(old) still succeeds and ResolveSlug returns redirected=false. This is friendlier than 301 for clients (no extra round-trip) but indicates the index update should be investigated as a follow-up — see products/dev/hub/backlog/pending/ for the index-cleanup ticket if/when one is opened. Functionally the outcome matches the #119 acceptance criterion: alias and canonical resolve to the same id.


OK_CANONICAL (28)

Slug Alias Hub version
koder-flow kflow v1.0.0
koder-hub khub v2.29.87
koder-eye retired → resolves to koder-konsul (Eye→Iris rename, iris#052; old slug reserved)
koder-konsul konsul v0.5.5 published (iris#052 done; live display name still "Koder Eye" — RenameApp display-name reconcile tracked by hub#805)
kode v1.7.9
koder-tools ktools v0.3.2
koder-sky ksky v1.0.2
koder-domains kdomains v1.0.0
koder-talk ktalk v1.0.1
koder-chat kchat v0.1.4
koder-drive kdrive v1.20.0
koder-calc kcalc v1.0.2
koder-beats kbeats v1.0.0
koder-craft kcraft v1.0.0
koder-dek kdek v1.9.44
koder-flash kflash v1.0.0
koder-sign ksign v1.0.2
koder-id kid v1.0.0
koder-pass kpass v1.0.6
koder-bot kbot v1.1.2
koder-cloud kcloud v1.0.0
koder-dash kdash v1.0.0
koder-term kterm v1.32.1 (renamed from kterm 2026-05-14)
koder-icon kicon v0.2.9 (renamed from kicon 2026-05-14)
koder-pkg kpkg v1.0.0 (renamed from koder-kpkg 2026-05-14)
koder-termux ktermux v0.118.3-koder1 (renamed from koder-ktermux 2026-05-14)
koder-mail kmail v1.0.0 (DUAL deduplicated 2026-05-14; kmail archived)
koder-kruze kruze v1.0.20 (added 2026-05-20; spot probe — Hub still serving 1.0.20 while Flow has v1.0.21v1.0.22v1.0.23 published. Hub-side bump tracked in products/horizontal/kruze/app/backlog/pending/157-hub-version-drift.md, blocked on Hub admin endpoint per products/dev/hub#116/#117/#103. Aliased probe kruze → 404 as expected; registry-canonical slug is koder-kruze)
kover v0.2.0 (added 2026-06-01 KOVER-038 @v0.0.1; bumped to v0.2.0 2026-06-02 KOVER-039 with the /k-commit kover release. Canonical slug is the bare kover per component-names.md, package_name=koder-kover. GET /api/v1/apps/kover → 200 status=published version=0.2.0. apt .deb install artifact published both arches POST /api/v1/publish/kover format=deb → 201 approved; amd64 live + downloadable at https://hub.koder.dev/apt/pool/main/k/kover/kover_0.2.0_amd64.deb (HTTP 200) and indexed koder-kover 0.2.0 in dists/stable/main/binary-amd64/Packages. arm64 published+approved but not yet surfaced in binary-arm64/Packagesaarch64→arm64 apt-view mapping is a Hub-side gap (hub-RFC-007), follow-up tracked for products/dev/hub, not a kover gap)

MISSING (89)

Most of these are aspirational components — registered in component-names.md for naming reservation but not yet shipped a first release. No action needed until the component has artifacts to publish.

Slug Alias
koder-bridge kbridge
kbox — (renamed from koder-box 2026-06-06; old name retired)
koder-dev kdev
koder-notebook knotebook
koder-pages kpages
koder-grid kgrid
koder-ci
krypt
koder-cal kcal
koder-board kboard
koder-cine kcine
koder-crew kcrew
koder-dok
koder-form kform
koder-hand khand
koder-imago
kall
kampus
kanvas
koru
koder-relay krelay

…and 69 others (full list regenerable from the probe script below).


Refresh procedure

# Extract (type, slug, alias) from registry
awk -F'|' '
  /^\| (product|service|engine|meta|umbrella) \|/ {
    type=$2; slug=$5; alias=$7
    gsub(/^[ \t]+|[ \t]+$/, "", type); gsub(/^[ \t]+|[ \t]+$/, "", slug); gsub(/^[ \t]+|[ \t]+$/, "", alias)
    n=split(alias, parts, ";"); first=parts[1]
    gsub(/^[ \t]+|[ \t]+$/, "", first)
    print type "\t" slug "\t" first
  }
' meta/docs/stack/registries/component-names.md > /tmp/registry-slugs.tsv

# Probe Hub (Python script, rate-limited with 0.3s + backoff on 429)
python3 - <<'PY'
import urllib.request, json, time
from urllib.error import HTTPError, URLError
def probe(s, retries=3):
    for a in range(retries):
        try:
            with urllib.request.urlopen(f"https://hub.koder.dev/api/v1/apps/{s}", timeout=10) as r:
                d = json.load(r); return 200, d.get('version','?')
        except HTTPError as e:
            if e.code == 429 and a < retries-1: time.sleep(2*(a+1)); continue
            return e.code, '-'
        except (URLError, Exception) as e: return -1, str(e)[:30]
with open('/tmp/registry-slugs.tsv') as f:
    for line in f:
        t, s, al = (line.rstrip().split('\t') + ['',''])[:3]
        if t not in ('product','service','engine'): continue
        sc, sv = probe(s); time.sleep(0.3)
        ac, av = probe(al) if al else ('', '');  time.sleep(0.3) if al else None
        ka = f"koder-{al}" if al and not al.startswith("koder-") else al
        kc, kv = (probe(ka) if (al and ka != s) else ('', ''))
        print(f"{t}\t{s}\t{al}\t{sc}({sv})\t{ac}({av})\t{kc}({kv})")
PY

  • registries/component-names.md — source of truth for slugs (authoritative)
  • specs/naming/forms.kmd — naming rules (R1-R6, T1-T11)
  • specs/binaries-and-cli/naming.kmd — binary name derivation (aliases[0] || slug)

Audit cadence

  • Refresh on every Hub publication/unpublication.
  • Drift cases block release engineering for affected components until

    the publication is re-aligned with component-names.md.

  • MISSING rows are informational only; not a block.

Companion CI workflow

.gitea/workflows/audit-hub-coverage.yml runs the probe automatically:

  • Schedule: daily at 04:17 BRT
  • Triggers: push touching component-names.md,

    koder-hub-coverage.md, or the workflow file itself; manual via workflow_dispatch

  • Output: state counts in workflow summary + drift/dual table

    attached as artifact (hub-coverage-tsv, 30-day retention)

  • Gate: soft-fails if DRIFT_AS + DUAL exceeds baseline (5) + 1,

    catching new drift without false-flagging the documented state. Tighten to "any drift = fail" once the 5 known cases are resolved.

Manual refresh of this document remains a human task — the workflow exposes regressions but does not auto-edit the prose.