[ka:cli-build-out] ka-promote: resolve manifest + concat scope-tagged series into cumulative patch #22
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Scope (child of #21)
First of three CLI verbs. Blocker for
ka-build/ka-installbecause both assume promotion has run.What ka-promote does
Read a
fleet/<host>.yamlmanifest, resolveincludes[]against the scope-tagged patch tree (patches/{soc,driver,board,module,subsystem,arch}/<scope>/<series>/), concatenate the referenced series in the order given by an explicitapply_orderfield (NOT alphabetical), validate the resulting cumulative patch applies cleanly onbaseline.ref, and emit:build/<host>/<baseline_ref>/0001-<host>-cumulative-series.patchbuild/<host>/<baseline_ref>/manifest.lock(resolved file list + per-file sha + apply_order)Phase plan
fleet/*.yaml, document the gaps vs. what ka-promote needs: explicitapply_orderfield, series-letter→series-name legend (currently lives inboltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/changelog perfleet/ohm.yaml), patch series file layout (.patchvsseriesfile inside the series dir),baseline.refhandling (git tag vs commit). Produce a substrate findings doc.baseline.ref, builds + boots the host without per-patch divergence vs the most recent hand-built kernel for that host. Phase-3 reference data: the hand-built cumulative patch checked into PKGBUILD repos.apply_order, optionalseries.lock), the resolver, the validator (git apply --checkagainst baseline checkout). Causal predictions written before code.apply_order: Nkeys).bin/ka-promotein the language already used inbin/ka-status(check first; probably bash or python).apply_orderwas added.Out of scope for this verb
marfrit-packages/arch/linux-pinetab2-danctnix-besser/(= #5, paused for BESser).ka-build(= separate child issue of #21).ka-install(= separate child issue of #21).Parity host choice — to lock in Phase 2
patches/board/pinebook-pro/, no DanctNIX rebase, simplest possible parity check. Risk: trivial substrate may not surface theapply_orderproblem.patches/{board/coolpi-cm5-genbook,module/coolpi-cm5,soc/rockchip/rk3588,driver/{mfd/rk8xx,gpu/drm},arch/arm64}), real apply_order surface, parity against the actual working v7.0-rc3 kernel.Phase 2 picks one. Probably ampere — better stress on the design, and the hand-rebuilt fresnel kernel is the documented manual recipe so we have the byte-truth to diff against either way.
Phase 0 — substrate findings
Manifest schema (observed across fleet/*.yaml)
includes:is a flat list. Two entry shapes coexist today:.patchboard/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch/driver/bes2600/staging-prep-series-danctnix/ka-promote must support both. Series-dir resolves to all
*.patchfiles in the dir excluding0000-cover-letter.patch(cover letters are metadata, not applied).Series directory layout (observed)
seriesfile. Filename-numeric prefix (0001-,0002-) is the within-series apply order — universally adopted (git format-patchdefault).Apply-order representation
Existing convention works without schema change. Manifest list order IS the canonical apply order. Re-reading
fleet/ohm.yaml, the include list is already arranged in apply order (staging-prep-series → pm-state-resync → pm-timeout-silence → pm-wake-consume-state → pm-gate-on-handshake → pm-detect-firmware-unsupported → scan-defer-backoff-tune → scan-defer-on-reject → lmac-recover → tx-sdio-dma-oob → factory-series → factory-thread-dev → factory-drop-kernel-write → drop-dpd-file-paths → drop-orphan-file-io → remove-chardev-user-interface → enable-testmode). That order matches the "A, B, C v3, F, G, D, E, C2, ..." legend from the danctnix-besser changelog modulo letter-name mapping.→ Decision (lock in Phase 4): No
apply_order:schema addition needed. ka-promote usesincludes[]list order directly. The ohm.yaml comment about a letter-legend is informational; the manifest is already self-describing.Two output shapes (PKGBUILDs in the wild)
source=()array0001-bes2600-besser-cumulative-series.patch(148 KB, 16-commits-squashed, singleFromheader — explicitly agit format-patch --stdoutsquash, NOT agit format-patchseries)→ Decision (lock in Phase 4): ka-promote emits both on every run:
build/<host>/<baseline_ref>/patches/— flat copy of every resolved .patch in apply order (renamed to apply-order-numeric prefix for unambiguous PKGBUILDsource=()consumption)build/<host>/<baseline_ref>/0001-<host>-cumulative-series.patch— one squashed cumulative diffbuild/<host>/<baseline_ref>/manifest.lock— resolved file list + per-file sha256 + apply_order + cumulative b2sumka-build picks the shape its target PKGBUILD wants. New PKGBUILDs default to cumulative (simpler template, single b2sum).
Cumulative-patch shape — ground truth
boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/0001-bes2600-besser-cumulative-series.patch:Fromheader (one squashed commit)git format-patch --stdout HEAD~16..HEADagainst a hand-rebased branch on boltzmann→ Phase-3 ground truth. ka-promote must produce a cumulative whose applied tree-state matches this (NOT necessarily byte-identical patch — squash commit metadata + author/date will differ deterministically). Equivalence test =
git apply <hand> + git diff-tree HEADvsgit apply <ka-promote> + git diff-tree HEADare identical trees.Tooling inventory (noether)
bash—bin/ka-statusalready in bash (18da673), same language for ka-promotepython3— used inside ka-status for JSON parsinggit— apply / format-patch / diff-tree availablesha256sum/b2sum— for manifest.lock + b2sum fieldpython3-yaml(or hand-rolled subset)Phase 0 status — DONE
Substrate locked. Open decisions deferred to Phase 4 (output-shape + manifest.lock format) but the design space is fully mapped — no surprises expected.
Moving to Phase 1 (formulate metric) + Phase 2 (pick parity host: fresnel vs ampere).
Phase 1 — metric locked
Acceptance metric (host-agnostic)
Given a parity host H with manifest M and baseline ref R,
ka-promote Hproduces:build/H/R/cumulative.patchbuild/H/R/patches/NNNN-*.patch(one file per resolved patch, NNNN = apply-order prefix)build/H/R/manifest.lock(resolved file list + per-file sha256 + apply_order + cumulative b2sum)Pass iff: on a fresh checkout of R,
git apply --check build/H/R/cumulative.patchsucceeds (clean apply, no rejects)git ls-files -s | sha256sum) equals the post-apply tree-hash of manually applying eachM.includes[i]patch in list order to the same fresh checkoutmanifest.lockis byte-stable across two consecutive runs on unchanged inputs (deterministic)manifest.lockmatchesb2sum < build/H/R/cumulative.patch0000-cover-letter.patchis excluded,0001-...,0002-...etc are included in filename orderWhat this metric does NOT yet test (deferred to ka-build / ka-install issues)
ka-promote is correct iff the applied-tree-state of its output equals the applied-tree-state of the manual flow. No build is needed to pass Phase 7 verify — pure source-tree-state check, fast (<30s per parity run).
Non-goals (explicit)
Phase 2 next
Pick parity host (fresnel vs ampere) based on substrate richness vs simplicity trade-off. fresnel = simpler (3+3 patches, no series-dir surface); ampere = richer (7 patches across 4 scopes). The metric works either way; choice drives which manual baseline we re-derive in Phase 3.
Leaning ampere because the verb interface must support series-dir includes for ohm/BESser eventually, and ampere stress-tests the multi-scope resolver path. If ampere parity proves hard to re-derive (= hand-built kernel for ampere has any drift vs manifest), fall back to fresnel.
Phase 2 — analysis
Parity-host candidates side-by-side
v7.0(torvalds/linux via mmind)v7.0-rc3(torvalds/linux)ampere-minimal-devicesbranch on linux-rk3588-marfritSeries-dir gap — neither parity host stresses it
Both parity-host candidates use only single-file includes. The series-dir resolver path (used by ohm/BESser exclusively today) goes untested if we only run parity against fresnel or ampere.
→ Add a synthetic resolver test as part of Phase 6 implement: tiny self-contained YAML pointing at a real series-dir under
patches/driver/bes2600/, expected-output a known list of0001-…,0002-…files in filename order with0000-cover-letter.patchexcluded. Pure resolver check, no patch-apply, no build. Lives intests/ka-promote/series-dir-fixture.yaml+ expected-output file.Decision
fresnel for Phase-3 baseline parity.
Rationale:
README.md:227+) is literally documented as the substrate ka-promote replaces — closing the loop on its own README is meta-correct.apply patches on baseline.ref→compare tree hash, no running-kernel check.ampere stays as the second parity host for the verify phase (Phase 7) to confirm the resolver handles a multi-scope include list as well as a flat-scope one. Series-dir parity covered by the synthetic fixture above.
Going into Phase 3
Phase 3 = baseline. Concrete next move: on noether, check out
v7.0(= fresnels baseline.ref), manually apply the 6 patches listed infleet/fresnel.yamlin list order, record the tree hash + per-patch sha256. That output becomes the ground-truthmanifest.lockka-promote must match. No code yet; pure measurement.If Phase-3 re-derivation surfaces any discrepancy (patch fails to apply, list-order ≠ working-order, missing patch in tree), thats a Phase-3 finding that loops back to Phase 0 substrate (patches missing, manifest wrong) before Phase 4 design.
Phase 3 — baseline derived + parity verified
Setup
On boltzmann:
git worktree add ~/src/kernel-agent-bootstrap/ka-promote-phase3/linux-v7.0 v7.0(torvalds/linux, commit3131ff5a117498bb4b9db3a238bb311cbf8383ce, tree6e2e13adb3ebf0a7a19583d58009f8372b592387).Substrate finding #1 — fresnel.yaml baseline.tree is wrong
git ls-remote https://git.kernel.org/.../mmind/linux-rockchip.git refs/tags/v7.0returns empty. mmind/linux-rockchip ships onlyv7.0-rc2,v7.0-rc3,v7.0-rockchip-drvfixes1,v7.0-rockchip-dtsfixes1-v2,v7.1-*— no plainv7.0tag. The actual baseline used in the 2026-05-09 bootstrap was torvalds v7.0; mmind is the patch-origin tree for the Rockchip context the patches were authored against, not the baseline tree.Fix (Phase 4 deliverable, not done yet):
baseline.tree: torvalds/linux+baseline.url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git. mmind can be referenced separately as apatch_authoring_context:annotation if useful. Patch out as part of the ka-promote PR (or sooner if convenient).Per-patch sha256 (input ground truth)
Iterative-apply ground truth
After applying each .patch in manifest list order with
git apply+git add -A+git write-tree:6e2e13adb3ebf0a7a19583d58009f8372b59238768d507338df545e1b5119ce97f09433718fe4ba193c1fb13a040b7e28e80066c89d33119c21727efaacbb4fae20527c8b9fb482d958334c7100a35971419f933f028856f34dd9d27fe1a4bd894e6c51bf0b07a3c5b7a172a4240a2d51bc9c1bd3190ea535e65ccdc95828c118c88b07ecba41976ed631d0bFiles touched:
arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts+ 5 files underdrivers/media/{common/videobuf2,platform/{rockchip/rga,verisilicon}}+include/media/videobuf2-core.h. 344 insertions, 4 deletions, 6 files.Phase 3 finding #2 — concat-in-list-order = full parity (algorithm simplification)
Implementation test:
Result tree:
5e65ccdc95828c118c88b07ecba41976ed631d0b— identical to iterative apply.→ ka-promote does NOT need
git format-patch, commit-rebase, or squash mechanics. Plaincatin list order is sufficient for parity.git applyresolves positional context per-hunk, independent of patch-blob boundaries. The 148-KB hand-built cumulative for besser (0001-bes2600-besser-cumulative-series.patchwith the "16 commits squashed" From-header) is overengineered for the parity goal — equally-valid output is a plain concat. ka-promote will emit concat; the human-readable "squashed commit" framing is a separate concern (commit-message authoring, not patch application).Phase 3 finding #3 — cumulative b2sum is deterministic
Given the per-input-patch sha256 table is stable and the concat order is deterministic from the manifest, this b2sum will be byte-stable across ka-promote runs — meets metric requirement #3 + #4 (
manifest.lockbyte-stability + b2sum stamp).Phase 3 status — DONE
Ground truth locked: final-tree
5e65ccdc95828c118c88b07ecba41976ed631d0b, cumulative b2sum4d9d93c655ea701b…. Algorithm path simplified (concat-in-order, no format-patch). Substrate finding #1 (mmind baseline.tree) logged for fix.Moving to Phase 4 — plan: design the resolver (single-file vs series-dir), the concat algorithm (trivial now), the manifest.lock writer, the validator (
git apply --checkon baseline.ref).Phase 4 — design / implementation plan
CLI surface (one verb, four flags)
Exit codes:
0success2missing input (manifest, patch file)3apply-check failed (only with--validate-against)4manifest parse/schema errorNo
--force, no--dry-run, no--quiet. The verb is deterministic, fast, and read-mostly (only writesbuild/<host>/<ref>/). Re-running overwrites the output directory.Resolver algorithm (single-file vs series-dir)
The series-dir excludes
0000-cover-letter.patchbecause the cover letter isgit format-patchmetadata ([PATCH 0/7] ...subject line, no actual diff hunks). Per Phase-0 inventory, this is the universal convention inpatches/driver/bes2600/*/.Cumulative algorithm (= Phase 3 finding #2)
That is it. No git format-patch. No commit-rebase. No mailbox splitting. Plain concatenation reproduces the iterative-apply tree-state byte-identically (verified Phase 3 against fresnel; final tree =
5e65ccdc95828c118c88b07ecba41976ed631d0b).Output directory layout
v1 does NOT emit flat per-patch files. ka-build (separate issue, follow-up) consumes
cumulative.patch + b2sum-from-lockonly. Flat output deferred to a--emit-flatflag in v2 if a PKGBUILD shape demands it; today every target consumer wants cumulative.build/added to.gitignoreat repo root in the same PR asbin/ka-promote.manifest.lock schema
Determinism contract: given byte-identical inputs (manifest + all referenced .patch files),
manifest.lockis byte-identical across runs.generated_atis the lone non-deterministic field; can be overridden viaSOURCE_DATE_EPOCHenv (reproducible-builds convention) if a downstream consumer needs perfect bit-stability. Default: real timestamp for human-readability.Validator (--validate-against)
Behavior:
cd /path/to/linux-checkout && git rev-parse HEAD^{tree}— capture current treegit rev-parse <baseline.ref>— verify the ref exists and points at the same commit/tree as HEAD (warn if they diverge, do not fail)git apply --check <output>/cumulative.patch— fail (exit 3) if apply would not be cleanValidator is optional — the verb succeeds without it.
--validate-againstis for CI gating and manual debugging; in normal use ka-build (next verb) runs the apply itself.Language / implementation choice
Bash script (matches
bin/ka-statusprecedent) that shells out to a smallpython3helper for YAML parse + manifest.lock YAML write. Pure-bash is awkward for YAML round-trip; pure-python loses the ka-status idiom. Splitting concerns: bash for CLI/dispatch/concat, python for YAML + sha256/b2sum (also fine in shell, but tighter alongside YAML).Dependencies (already present on every target host per Phase-0 tooling inventory):
bash,python3,python3-yaml,coreutils(sha256sum, b2sum),git(only for--validate-against).Tests / fixtures (Phase 6 implement deliverables)
ka-promote fresnelthen diff cumulative b2sum against the recorded4d9d93c655ea701b…. PASS = exact match.ka-promote amperethen diff against a Phase-3-style re-derived baseline for ampere (done as part of Phase 7 verify, not Phase 6).tests/ka-promote/fixtures/series-dir.yamlcontaining one includedriver/bes2600/staging-prep-series-danctnix/. Expected output: 6 resolved patches in filename order (0001-…through0006-…),0000-cover-letter.patchexcluded. Pure resolver check, no apply..patchor/exits 4.Side-deliverable: fix Phase-3 finding #1
In the same PR, fix
fleet/fresnel.yaml:Same field hygiene for ampere/ohm if survey shows similar discrepancy. (Quick survey result from Phase 3 fetch: ampere already uses
torvalds/linux+v7.0-rc3correctly. ohm usesDanctNIX/linux-pinetab2+v7.0-danctnix1— DanctNIX-specific tag, would need verification on their codeberg fetch but plausible.)Phase 4 status — DONE
Plan locked. Moving to Phase 5 — outside-look review of this plan before Phase 6 implement.
Phase 5 reviewer choice: Janet (RE/ARM specialist) is the wrong fit for CLI/build-tool review. Use generic agent or self-review with explicit outside-look discipline. Open to either.
Phase 5 review (sonnet outside-look)
Soundness
The plan achieves the metric. Resolver → concat → lock-write pipeline is complete; the Phase-3 empirical proof (concat tree = iterative-apply tree) removes the hardest risk. Parity guarantee holds without gaps for the single-file case. The one structural gap is finding #1 below.
Findings
1.
staging-prep-series-danctnixhas 7 patches, not 6 — cover-letter filter mask is fine, but the series-dir test fixture count is wrong in the plan.Phase 4 plan says "Expected output: 6 resolved patches". The actual dir has 7 (0001–0007). Fix the fixture expected-count to 7 before wiring the test or the test will fail on first run for the wrong reason.
2.
README.mdfiles inside series-dirs are not excluded by the current filter.The glob
*.patchalready excludes them (README.md doesn't match), sostaging-prep-series-danctnix/is safe. But the scope-root READMEs live one level up, not inside series-dirs, so they never reach the glob. No code change needed — just confirm the*.patchglob is the one used, not*with a name-filter. The plan usesglob src/*.patchwhich is correct. Noting this so the implementer doesn't widen the glob later without knowing the invariant.3.
--validate-againststep 2 does a loose identity check but doesn't fail on divergence — that's probably wrong for CI use.The plan says "warn if they diverge, do not fail". If the checkout is sitting on the wrong commit (e.g., user forgot to
git checkout v7.0), the apply-check passes but produces a meaningless result. For CI gating, a diverged-tree-warn-not-fail is a footgun. Suggested fix:Or document "only pass
--validate-againston a clean checkout ofbaseline.ref" and make divergence a hard error always. Either is fine; current "warn, proceed" is a trap.4. Determinism:
manifest.sha256is computed over the source manifest file, which contains comments and YAML ordering that can drift independently of content semantics.This is fine as a content-hash (any edit = new hash, which is the desired behavior). Just confirm the implementation hashes the file bytes, not a parsed+re-serialized form. If python yaml round-trips the manifest for any reason before hashing, ordering non-determinism can creep in. Hash the raw file:
hashlib.sha256(open(path,'rb').read()).hexdigest().5. Duplicate
includesentries are silently applied twice.The resolver has no dedup check.
git applywill fail mid-way ("already applied" or hunk reject) with an unhelpful error. Worth a pre-flight check:Exit 4 (manifest parse/schema error) is the right code. Not blocking for v1 if you want to punt it, but it's a 4-line add.
Things I checked and look fine
0000-filter is justified by two independent series dirs (staging-prep-seriesandstaging-prep-series-danctnix), both using the same convention. Not a single-data-point risk.*.patchglob handles this structurally. README.md, MAINTAINERS, etc. never match.git applynormalises line endings. Not a ka-promote concern.generated_atas sole non-determinism: confirmed. Pythonyaml.dumpwithdefault_flow_style=Falseand an explicitsort_keys=True(or sorted dict construction) is stable across runs. If PyYAML's defaultsort_keys=Trueis relied on, note that in a code comment so nobody removes it.--validate-againstusing a path not remote+ref: correct call. Requiring a local checkout keeps the verb offline-capable and fast. The setup cost (onegit worktree add) is documented in Phase 3 and is mechanical.build/gitignore: straightforward, not a gap.Verdict
Green with followups. Two are Phase 6 implement TODOs (fix fixture patch count #1, harden validate-against divergence check #3). Two are optional-but-cheap adds (#4 hash raw bytes — confirm not round-trip; #5 dedup check). Nothing blocks Phase 6.
Follow-up TODO list for the implement phase:
--validate-againstdivergence = hard error (or at minimum documented hard-error in CI)yaml.dump(..., sort_keys=True)explicit in the lock-writer (don't leave it implicit)Phase 0 correction (caught by Phase-5 reviewer)
The series-dir example in Phase 0 stated 8 .patch files / 6 applied for
staging-prep-series-danctnix. Actual is 8 files / 7 applied (0000-cover-letter + 0001..0007). The Phase-4 plan inherited the wrong count.→ Phase 6 fixture spec:
tests/ka-promote/fixtures/series-dir.yamlresolves to 7 patches, not 6. Updating implementation TODO list accordingly.Root cause:
ls -la | head -10in the Phase-0 probe truncated at file 0006 (10 lines =total,.,.., then 7 entries 0000-0006), and I read the truncation as the full list.ls | wc -lwould have shown 8.Phase 6 — implement: done, committed, pushed
Branch:
claude-noether/kernel-agent:noether/ka-promote(commitbeadf24). 500 insertions / 6 deletions across 8 files.Files
bin/ka-promotetests/ka-promote/run-tests.shtests/ka-promote/fixtures/series-dir.yamltests/ka-promote/fixtures/bad-include.yamltests/ka-promote/fixtures/missing-patch.yamltests/ka-promote/fixtures/duplicate-include.yaml.gitignorefleet/fresnel.yamlTest results (noether)
Validator smoke (boltzmann, against
~/src/kernel-agent-bootstrap/ka-promote-phase3/linux-v7.0)v7.0worktreePhase-5 followups status
--validate-againstdivergence = hard error (plus added dirty-tree hard error caught during smoke testing)hashlib.sha256(raw).hexdigest())yaml.dump(..., sort_keys=True)explicitLanguage deviation from ka-status
ka-status is bash; ka-promote is pure python3. Justified in the commit message: YAML in/out + dict construction + per-file hashing made bash+heredoc-python quoting hell with no readability gain. Dependencies (python3 + python3-yaml, both apt-default on Debian/Ubuntu, AUR-default on Arch) are already present on every target host. python3-yaml had to be apt-installed on noether (was missing) — noting as a follow-up for the
marfrit-packageskernel-agentpackage dependency list if/when one exists.Phase 7 next
Verify ampere parity. Plan:
ka-promote ampereagainst that worktree with--validate-against.After Phase 7 → Phase 8 close: README rewrite (remove the "manual substitute for ka-promote" entry from Bootstrap reference build section), open PR against marfrit/kernel-agent main.
Phase 7 — verify: ampere multi-scope parity PASS
Setup
torvalds/linux v7.0-rc3(commit3daa4f5dc6cc) worktree atboltzmann:~/src/kernel-agent-bootstrap/ka-promote-phase3/linux-v7.0-rc3origin/mainto pick up PR #20 (genbook-internal-mic) — without the rebase, my noether/ka-promote branch was missing the 0011 entry in fleet/ampere.yaml. Rebase + force-push done (91fe815replacesbeadf24).soc/rockchip/rk3588/×1,board/coolpi-cm5-genbook/×5,module/coolpi-cm5/×1.Three-way comparison
Confirmed
--validate-againston a clean checkout ofbaseline.refexits 0.Whitespace observation (non-blocking)
patches/board/coolpi-cm5-genbook/0011-…wire-internal-microphone.patch:63has anew blank line at EOFwhitespace warning whengit applyruns. Patch still applies cleanly. Not a ka-promote concern; if it bothers downstream consumers, the fix is to re-export the patch viagit format-patchafter stripping the trailing blank line. Logging here so it doesnt surprise the next reader; not opening a follow-up issue.Tests on noether (after rebase, still all green)
Phase 7 status — DONE
Multi-scope parity (ampere) + single-scope parity (fresnel) + validator all-green on the real torvalds checkouts. Two independent hosts, two independent baselines, two byte-equal b2sums.
Next: Phase 8 close — README rewrite (remove "manual substitute for ka-promote" from Bootstrap reference build section), open PR
claude-noether/kernel-agent:noether/ka-promote→marfrit/kernel-agent:main, close this issue on merge.Phase 8 — close: PR opened, README rewritten
PR: marfrit/kernel-agent#23 —
claude-noether:noether/ka-promote→marfrit:main.Final commit set on branch
91fe8153c6d356README changes
ka-promote <host>marked[bin/ka-promote — implemented Phase 6, issue #22].ka-build/ka-installget explicit "next/last verb to implement" markers.ka-promote <campaign> <patch-or-glob> --to <scope>semantic renamed toka-import(still unimplemented; today via manual git+PR workflow). Note paragraph added explaining the rename.ka-promoterow marked automated 2026-05-18.What remains for the umbrella
ka-build— next verb. Renders PKGBUILD template (b2sum + pkgrel + _srctag stamping), invokes makepkg natively on assigned build host, optionally signs + publishes via marfrit-publish-arch.ka-install— last verb. scp + pacman -U + extlinux/mkinitcpio post-trigger + ssh heartbeat as consent-via-action.ka-import— bonus, low priority. The "promote patches from campaign repo into scope-tagged tree" operation that ka-promote used to be named for. Today: regular git+PR workflow does this. Worth packaging if/when a campaign repeats the import pattern often.Closing #22 on PR #23 merge.