[ka:cli-build-out] ka-promote: resolve manifest + concat scope-tagged series into cumulative patch #22

Closed
opened 2026-05-18 07:59:12 +00:00 by marfrit · 10 comments
Owner

Scope (child of #21)

First of three CLI verbs. Blocker for ka-build / ka-install because both assume promotion has run.

What ka-promote does

Read a fleet/<host>.yaml manifest, resolve includes[] 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 explicit apply_order field (NOT alphabetical), validate the resulting cumulative patch applies cleanly on baseline.ref, and emit:

  • build/<host>/<baseline_ref>/0001-<host>-cumulative-series.patch
  • build/<host>/<baseline_ref>/manifest.lock (resolved file list + per-file sha + apply_order)
  • b2sum of the cumulative patch (for PKGBUILD/source-array stamping by ka-build)

Phase plan

  • Phase 0 (substrate / inventory) — read all fleet/*.yaml, document the gaps vs. what ka-promote needs: explicit apply_order field, series-letter→series-name legend (currently lives in boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/ changelog per fleet/ohm.yaml), patch series file layout (.patch vs series file inside the series dir), baseline.ref handling (git tag vs commit). Produce a substrate findings doc.
  • Phase 1 (formulate metric) — lock: ka-promote on a parity host (fresnel or ampere; NOT ohm — BESser is iterating there per umbrella #21) produces a cumulative patch + manifest.lock that, applied on 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.
  • Phase 2 (analyze) — pick the parity host. fresnel has the cleanest substrate (3 patches, simple ordering, no DanctNIX rebase). ampere has the most realistic substrate (multi-scope, real apply_order, parity check against a working kernel). Decision in phase2.
  • Phase 3 (baseline) — re-derive the cumulative patch for the chosen host by hand, byte-compare against the canonical one. Establishes the ground truth ka-promote will be diffed against. If byte-identity is unattainable (whitespace, From: header normalization), define the equivalence class for parity.
  • Phase 4 (plan) — design the manifest schema additions (apply_order, optional series.lock), the resolver, the validator (git apply --check against baseline checkout). Causal predictions written before code.
  • Phase 5 (review) — outside-look pass on the schema + verb interface; especially the apply_order representation (inline list vs per-include apply_order: N keys).
  • Phase 6 (implement)bin/ka-promote in the language already used in bin/ka-status (check first; probably bash or python).
  • Phase 7 (verify) — produce parity patch on chosen host. Equal to phase-3 ground truth.
  • Phase 8 (close) — PR, README rewrite (remove "manual substitute for ka-promote" entry from Bootstrap reference build section), update parity host manifest if apply_order was added.
  • Phase 9 (memory) — feedback if anything surprising.

Out of scope for this verb

  • ohm migration / PKGBUILD relocation to 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).
  • kconfig-by-manifest.

Parity host choice — to lock in Phase 2

  • fresnel (Pinebook Pro / RK3399): 3 patches in patches/board/pinebook-pro/, no DanctNIX rebase, simplest possible parity check. Risk: trivial substrate may not surface the apply_order problem.
  • ampere (CoolPi GenBook / RK3588): multi-scope (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.

## Scope (child of #21) First of three CLI verbs. Blocker for `ka-build` / `ka-install` because both assume promotion has run. ## What ka-promote does Read a `fleet/<host>.yaml` manifest, resolve `includes[]` 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 explicit `apply_order` field (NOT alphabetical), validate the resulting cumulative patch applies cleanly on `baseline.ref`, and emit: - `build/<host>/<baseline_ref>/0001-<host>-cumulative-series.patch` - `build/<host>/<baseline_ref>/manifest.lock` (resolved file list + per-file sha + apply_order) - b2sum of the cumulative patch (for PKGBUILD/source-array stamping by ka-build) ## Phase plan - **Phase 0 (substrate / inventory)** — read all `fleet/*.yaml`, document the gaps vs. what ka-promote needs: explicit `apply_order` field, series-letter→series-name legend (currently lives in `boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/` changelog per `fleet/ohm.yaml`), patch series file layout (`.patch` vs `series` file inside the series dir), `baseline.ref` handling (git tag vs commit). Produce a substrate findings doc. - **Phase 1 (formulate metric)** — lock: ka-promote on a *parity host* (fresnel or ampere; **NOT ohm** — BESser is iterating there per umbrella #21) produces a cumulative patch + manifest.lock that, applied on `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. - **Phase 2 (analyze)** — pick the parity host. fresnel has the cleanest substrate (3 patches, simple ordering, no DanctNIX rebase). ampere has the most realistic substrate (multi-scope, real apply_order, parity check against a working kernel). Decision in phase2. - **Phase 3 (baseline)** — re-derive the cumulative patch for the chosen host by hand, byte-compare against the canonical one. Establishes the ground truth ka-promote will be diffed against. If byte-identity is unattainable (whitespace, From: header normalization), define the equivalence class for parity. - **Phase 4 (plan)** — design the manifest schema additions (`apply_order`, optional `series.lock`), the resolver, the validator (`git apply --check` against baseline checkout). Causal predictions written before code. - **Phase 5 (review)** — outside-look pass on the schema + verb interface; especially the apply_order representation (inline list vs per-include `apply_order: N` keys). - **Phase 6 (implement)** — `bin/ka-promote` in the language already used in `bin/ka-status` (check first; probably bash or python). - **Phase 7 (verify)** — produce parity patch on chosen host. Equal to phase-3 ground truth. - **Phase 8 (close)** — PR, README rewrite (remove "manual substitute for ka-promote" entry from Bootstrap reference build section), update parity host manifest if `apply_order` was added. - **Phase 9 (memory)** — feedback if anything surprising. ## Out of scope for this verb - ohm migration / PKGBUILD relocation to `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). - kconfig-by-manifest. ## Parity host choice — to lock in Phase 2 - **fresnel** (Pinebook Pro / RK3399): 3 patches in `patches/board/pinebook-pro/`, no DanctNIX rebase, simplest possible parity check. Risk: trivial substrate may not surface the `apply_order` problem. - **ampere** (CoolPi GenBook / RK3588): multi-scope (`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.
Author
Owner

Phase 0 — substrate findings

Manifest schema (observed across fleet/*.yaml)

includes: is a flat list. Two entry shapes coexist today:

Shape Used by Example
single-file — path ending in .patch fresnel, ampere board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch
series-dir — path ending in / ohm driver/bes2600/staging-prep-series-danctnix/

ka-promote must support both. Series-dir resolves to all *.patch files in the dir excluding 0000-cover-letter.patch (cover letters are metadata, not applied).

Series directory layout (observed)

patches/driver/bes2600/staging-prep-series-danctnix/
├── 0000-cover-letter.patch    # NOT applied
├── 0001-bes2600-use-request_firmware-for-factory.txt-read.patch
├── 0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch
├── 0003-...
└── 0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch
  • No quilt-style series file. Filename-numeric prefix (0001-, 0002-) is the within-series apply order — universally adopted (git format-patch default).
  • Series sizes observed: 1 to 8 patches. README.md sits at the scope root, not the series root.

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 uses includes[] 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)

Host PKGBUILD shape Cumulative patch?
fresnel flat — each patch separate in source=() array no
ampere flat (same as fresnel — based on the manual fresnel bootstrap recipe) no
ohm / besser one cumulative patch yes — 0001-bes2600-besser-cumulative-series.patch (148 KB, 16-commits-squashed, single From header — explicitly a git format-patch --stdout squash, NOT a git format-patch series)

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 PKGBUILD source=() consumption)
  • build/<host>/<baseline_ref>/0001-<host>-cumulative-series.patch — one squashed cumulative diff
  • build/<host>/<baseline_ref>/manifest.lock — resolved file list + per-file sha256 + apply_order + cumulative b2sum

ka-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:

  • 148149 bytes, single From header (one squashed commit)
  • Hand-authored subject + description block listing the 16 component series with their letter-name mapping
  • Generated by git format-patch --stdout HEAD~16..HEAD against 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 HEAD vs git apply <ka-promote> + git diff-tree HEAD are identical trees.

Tooling inventory (noether)

  • bashbin/ka-status already in bash (18da673), same language for ka-promote
  • python3 — used inside ka-status for JSON parsing
  • git — apply / format-patch / diff-tree available
  • sha256sum / b2sum — for manifest.lock + b2sum field
  • YAML parser — ka-status uses python json (manifests are read by humans currently); needs python3-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 0 — substrate findings ### Manifest schema (observed across fleet/*.yaml) `includes:` is a flat list. Two entry shapes coexist today: | Shape | Used by | Example | |---|---|---| | **single-file** — path ending in `.patch` | fresnel, ampere | `board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch` | | **series-dir** — path ending in `/` | ohm | `driver/bes2600/staging-prep-series-danctnix/` | ka-promote must support both. Series-dir resolves to all `*.patch` files in the dir **excluding `0000-cover-letter.patch`** (cover letters are metadata, not applied). ### Series directory layout (observed) ``` patches/driver/bes2600/staging-prep-series-danctnix/ ├── 0000-cover-letter.patch # NOT applied ├── 0001-bes2600-use-request_firmware-for-factory.txt-read.patch ├── 0002-bes2600-default-STANDARD_FACTORY_EFUSE_FLAG-off-for-.patch ├── 0003-... └── 0006-bes2600-enable-CONFIG_BES2600_TESTMODE-by-default-fi.patch ``` - No quilt-style `series` file. Filename-numeric prefix (`0001-`, `0002-`) is the within-series apply order — universally adopted (`git format-patch` default). - Series sizes observed: 1 to 8 patches. README.md sits at the scope root, not the series root. ### 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 uses `includes[]` 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) | Host | PKGBUILD shape | Cumulative patch? | |---|---|---| | fresnel | flat — each patch separate in `source=()` array | no | | ampere | flat (same as fresnel — based on the manual fresnel bootstrap recipe) | no | | ohm / besser | one cumulative patch | yes — `0001-bes2600-besser-cumulative-series.patch` (148 KB, 16-commits-squashed, single `From ` header — explicitly a `git format-patch --stdout` squash, NOT a `git format-patch` series) | → **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 PKGBUILD `source=()` consumption) - `build/<host>/<baseline_ref>/0001-<host>-cumulative-series.patch` — one squashed cumulative diff - `build/<host>/<baseline_ref>/manifest.lock` — resolved file list + per-file sha256 + apply_order + cumulative b2sum ka-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`: - 148149 bytes, **single `From ` header** (one squashed commit) - Hand-authored subject + description block listing the 16 component series with their letter-name mapping - Generated by `git format-patch --stdout HEAD~16..HEAD` against 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 HEAD` vs `git apply <ka-promote> + git diff-tree HEAD` are identical trees. ### Tooling inventory (noether) - [x] `bash` — `bin/ka-status` already in bash (18da673), same language for ka-promote - [x] `python3` — used inside ka-status for JSON parsing - [x] `git` — apply / format-patch / diff-tree available - [x] `sha256sum` / `b2sum` — for manifest.lock + b2sum field - [ ] YAML parser — ka-status uses python json (manifests are read by humans currently); needs `python3-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).
Author
Owner

Phase 1 — metric locked

Acceptance metric (host-agnostic)

Given a parity host H with manifest M and baseline ref R, ka-promote H produces:

  • build/H/R/cumulative.patch
  • build/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,

  1. git apply --check build/H/R/cumulative.patch succeeds (clean apply, no rejects)
  2. The post-apply tree-hash (git ls-files -s | sha256sum) equals the post-apply tree-hash of manually applying each M.includes[i] patch in list order to the same fresh checkout
  3. manifest.lock is byte-stable across two consecutive runs on unchanged inputs (deterministic)
  4. The cumulative b2sum stamped in manifest.lock matches b2sum < build/H/R/cumulative.patch
  5. Series-dir includes resolve correctly: 0000-cover-letter.patch is excluded, 0001-..., 0002-... etc are included in filename order

What this metric does NOT yet test (deferred to ka-build / ka-install issues)

  • Whether the resulting kernel boots (= ka-build / ka-install)
  • Whether modules signed correctly (= ka-build)
  • PKGBUILD template rendering with cumulative b2sum substitution (= ka-build)

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)

  • Byte-identical patch output vs hand-built cumulative. Squash commit metadata (author, date, sha) WILL differ; equivalence is at the applied tree-state level, not the patch-blob level.
  • Cross-host parity. Each host is its own parity check; passing fresnel does not certify ampere automatically.

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 1 — metric locked ### Acceptance metric (host-agnostic) Given a parity host **H** with manifest **M** and baseline ref **R**, `ka-promote H` produces: - `build/H/R/cumulative.patch` - `build/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, 1. `git apply --check build/H/R/cumulative.patch` succeeds (clean apply, no rejects) 2. The post-apply tree-hash (`git ls-files -s | sha256sum`) equals the post-apply tree-hash of manually applying each `M.includes[i]` patch in list order to the same fresh checkout 3. `manifest.lock` is byte-stable across two consecutive runs on unchanged inputs (deterministic) 4. The cumulative b2sum stamped in `manifest.lock` matches `b2sum < build/H/R/cumulative.patch` 5. Series-dir includes resolve correctly: `0000-cover-letter.patch` is excluded, `0001-...`, `0002-...` etc are included in filename order ### What this metric does NOT yet test (deferred to ka-build / ka-install issues) - Whether the resulting kernel **boots** (= ka-build / ka-install) - Whether modules signed correctly (= ka-build) - PKGBUILD template rendering with cumulative b2sum substitution (= ka-build) 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) - Byte-identical patch output vs hand-built cumulative. Squash commit metadata (author, date, sha) WILL differ; equivalence is at the *applied tree-state* level, not the patch-blob level. - Cross-host parity. Each host is its own parity check; passing fresnel does not certify ampere automatically. ### 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.
Author
Owner

Phase 2 — analysis

Parity-host candidates side-by-side

fresnel ampere
baseline.ref v7.0 (torvalds/linux via mmind) v7.0-rc3 (torvalds/linux)
patches 6 (3 board + 3 subsystem/media/videobuf2/dma-resv-release-fence) 7 (1 soc + 1 module + 5 board)
scopes hit 2 (board, subsystem) 4 (soc, module, board ×2)
series-dirs in include 0 0
working built kernel ref linux-fresnel-fourier (manual bootstrap 2026-05-09, pkgrel=14 on boltzmann) linux-ampere-fourier / ampere-minimal-devices branch on linux-rk3588-marfrit
risk trivial substrate, may not stress resolver manifest patch numbering non-contiguous (0001/2/3/4/5/8/11), but list-order = filename-order = apply-order

Series-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 of 0001-…, 0002-… files in filename order with 0000-cover-letter.patch excluded. Pure resolver check, no patch-apply, no build. Lives in tests/ka-promote/series-dir-fixture.yaml + expected-output file.

Decision

fresnel for Phase-3 baseline parity.

Rationale:

  • Cleanest substrate. 6 patches, no scope contention, list-order is uncontroversial. Phase-3 re-derivation is mechanical.
  • The 2026-05-09 manual-bootstrap recipe (README.md:227+) is literally documented as the substrate ka-promote replaces — closing the loop on its own README is meta-correct.
  • Risk that fresnels running kernel has drifted from the manifest is irrelevant: the metric is purely apply patches on baseline.refcompare 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 in fleet/fresnel.yaml in list order, record the tree hash + per-patch sha256. That output becomes the ground-truth manifest.lock ka-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 2 — analysis ### Parity-host candidates side-by-side | | fresnel | ampere | |---|---|---| | baseline.ref | `v7.0` (torvalds/linux via mmind) | `v7.0-rc3` (torvalds/linux) | | patches | 6 (3 board + 3 subsystem/media/videobuf2/dma-resv-release-fence) | 7 (1 soc + 1 module + 5 board) | | scopes hit | 2 (board, subsystem) | 4 (soc, module, board ×2) | | series-dirs in include | **0** | **0** | | working built kernel ref | linux-fresnel-fourier (manual bootstrap 2026-05-09, pkgrel=14 on boltzmann) | linux-ampere-fourier / `ampere-minimal-devices` branch on linux-rk3588-marfrit | | risk | trivial substrate, may not stress resolver | manifest patch numbering non-contiguous (0001/2/3/4/5/8/11), but list-order = filename-order = apply-order | ### Series-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 of `0001-…`, `0002-…` files in filename order with `0000-cover-letter.patch` excluded. Pure resolver check, no patch-apply, no build. Lives in `tests/ka-promote/series-dir-fixture.yaml` + expected-output file. ### Decision **fresnel** for Phase-3 baseline parity. Rationale: - Cleanest substrate. 6 patches, no scope contention, list-order is uncontroversial. Phase-3 re-derivation is mechanical. - The 2026-05-09 manual-bootstrap recipe (`README.md:227+`) is *literally* documented as the substrate ka-promote replaces — closing the loop on its own README is meta-correct. - Risk that fresnels running kernel has drifted from the manifest is irrelevant: the metric is purely `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 in `fleet/fresnel.yaml` in list order, record the tree hash + per-patch sha256. That output becomes the ground-truth `manifest.lock` ka-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.
Author
Owner

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, commit 3131ff5a117498bb4b9db3a238bb311cbf8383ce, tree 6e2e13adb3ebf0a7a19583d58009f8372b592387).

Substrate finding #1 — fresnel.yaml baseline.tree is wrong

baseline:
  tree: mmind/linux-rockchip
  ref: v7.0

git ls-remote https://git.kernel.org/.../mmind/linux-rockchip.git refs/tags/v7.0 returns empty. mmind/linux-rockchip ships only v7.0-rc2, v7.0-rc3, v7.0-rockchip-drvfixes1, v7.0-rockchip-dtsfixes1-v2, v7.1-* — no plain v7.0 tag. 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 a patch_authoring_context: annotation if useful. Patch out as part of the ka-promote PR (or sooner if convenient).

Per-patch sha256 (input ground truth)

e5a89ed6e4786567df65586da2d4f3ebbd659e9157ec83fdb9e460f5e7175c75  board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch
18877de3a938ef458acca855e98be902b868aedcd30a7db7437fc7203bf42ac9  board/pinebook-pro/0002-arm64-dts-rk3399-pinebook-pro-enable-hdmi-sound.patch
e2ba274b3d8913f8ddc667bec761b3f86b7a02a0d7f0221abb992a32ba324e42  board/pinebook-pro/0003-arm64-dts-rk3399-pinebook-pro-spi1-max-freq-10MHz.patch
792829078244027871a1127f5e41e64dc2d12d012eb64afb6d01d387a7fd7ac9  subsystem/media/videobuf2/dma-resv-release-fence/0004-media-videobuf2-add-opt-in-dma_resv-producer-fence-h.patch
2f1e63aa90d7082055e901648608a6b6ab74ebc71737c25ba44663f606239bbe  subsystem/media/videobuf2/dma-resv-release-fence/0005-media-hantro-attach-dma_resv-release-fence-at-device.patch
ff6298652e402a398a2751095ebb6ecdc77795f0466d9b393a3ceee1252b8088  subsystem/media/videobuf2/dma-resv-release-fence/0006-media-rockchip-rga-attach-dma_resv-release-fence-at-.patch

Iterative-apply ground truth

After applying each .patch in manifest list order with git apply + git add -A + git write-tree:

step tree-after
baseline (v7.0) 6e2e13adb3ebf0a7a19583d58009f8372b592387
01 68d507338df545e1b5119ce97f09433718fe4ba1
02 93c1fb13a040b7e28e80066c89d33119c21727ef
03 aacbb4fae20527c8b9fb482d958334c7100a3597
04 1419f933f028856f34dd9d27fe1a4bd894e6c51b
05 f0b07a3c5b7a172a4240a2d51bc9c1bd3190ea53
06 (FINAL) 5e65ccdc95828c118c88b07ecba41976ed631d0b

Files touched: arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts + 5 files under drivers/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:

cat patches/01.patch patches/02.patch ... patches/06.patch > cumulative.patch
git reset --hard v7.0 && git clean -fdx
git apply cumulative.patch
git add -A && git write-tree

Result tree: 5e65ccdc95828c118c88b07ecba41976ed631d0bidentical to iterative apply.

→ ka-promote does NOT need git format-patch, commit-rebase, or squash mechanics. Plain cat in list order is sufficient for parity. git apply resolves positional context per-hunk, independent of patch-blob boundaries. The 148-KB hand-built cumulative for besser (0001-bes2600-besser-cumulative-series.patch with 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

b2sum fresnel-cumulative.patch
4d9d93c655ea701b587bf1383c794f41b1aeb3bc32bca69ce3488852ec2c1474a2f47585608598b39ac05671490b8df63c5bc7d093f87e1afd5a92f908891b67

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.lock byte-stability + b2sum stamp).

Phase 3 status — DONE

Ground truth locked: final-tree 5e65ccdc95828c118c88b07ecba41976ed631d0b, cumulative b2sum 4d9d93c655ea701b…. 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 --check on baseline.ref).

## 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, commit `3131ff5a117498bb4b9db3a238bb311cbf8383ce`, tree `6e2e13adb3ebf0a7a19583d58009f8372b592387`). ### Substrate finding #1 — fresnel.yaml baseline.tree is wrong ```yaml baseline: tree: mmind/linux-rockchip ref: v7.0 ``` `git ls-remote https://git.kernel.org/.../mmind/linux-rockchip.git refs/tags/v7.0` returns **empty**. mmind/linux-rockchip ships only `v7.0-rc2`, `v7.0-rc3`, `v7.0-rockchip-drvfixes1`, `v7.0-rockchip-dtsfixes1-v2`, `v7.1-*` — no plain `v7.0` tag. 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 a `patch_authoring_context:` annotation if useful. Patch out as part of the ka-promote PR (or sooner if convenient). ### Per-patch sha256 (input ground truth) ``` e5a89ed6e4786567df65586da2d4f3ebbd659e9157ec83fdb9e460f5e7175c75 board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch 18877de3a938ef458acca855e98be902b868aedcd30a7db7437fc7203bf42ac9 board/pinebook-pro/0002-arm64-dts-rk3399-pinebook-pro-enable-hdmi-sound.patch e2ba274b3d8913f8ddc667bec761b3f86b7a02a0d7f0221abb992a32ba324e42 board/pinebook-pro/0003-arm64-dts-rk3399-pinebook-pro-spi1-max-freq-10MHz.patch 792829078244027871a1127f5e41e64dc2d12d012eb64afb6d01d387a7fd7ac9 subsystem/media/videobuf2/dma-resv-release-fence/0004-media-videobuf2-add-opt-in-dma_resv-producer-fence-h.patch 2f1e63aa90d7082055e901648608a6b6ab74ebc71737c25ba44663f606239bbe subsystem/media/videobuf2/dma-resv-release-fence/0005-media-hantro-attach-dma_resv-release-fence-at-device.patch ff6298652e402a398a2751095ebb6ecdc77795f0466d9b393a3ceee1252b8088 subsystem/media/videobuf2/dma-resv-release-fence/0006-media-rockchip-rga-attach-dma_resv-release-fence-at-.patch ``` ### Iterative-apply ground truth After applying each .patch in manifest list order with `git apply` + `git add -A` + `git write-tree`: | step | tree-after | |---|---| | baseline (v7.0) | `6e2e13adb3ebf0a7a19583d58009f8372b592387` | | 01 | `68d507338df545e1b5119ce97f09433718fe4ba1` | | 02 | `93c1fb13a040b7e28e80066c89d33119c21727ef` | | 03 | `aacbb4fae20527c8b9fb482d958334c7100a3597` | | 04 | `1419f933f028856f34dd9d27fe1a4bd894e6c51b` | | 05 | `f0b07a3c5b7a172a4240a2d51bc9c1bd3190ea53` | | 06 (FINAL) | **`5e65ccdc95828c118c88b07ecba41976ed631d0b`** | Files touched: `arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts` + 5 files under `drivers/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: ```bash cat patches/01.patch patches/02.patch ... patches/06.patch > cumulative.patch git reset --hard v7.0 && git clean -fdx git apply cumulative.patch git add -A && git write-tree ``` Result tree: `5e65ccdc95828c118c88b07ecba41976ed631d0b` — **identical** to iterative apply. → ka-promote does NOT need `git format-patch`, commit-rebase, or squash mechanics. Plain `cat` in list order is sufficient for parity. `git apply` resolves positional context per-hunk, independent of patch-blob boundaries. The 148-KB hand-built cumulative for besser (`0001-bes2600-besser-cumulative-series.patch` with 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 ``` b2sum fresnel-cumulative.patch 4d9d93c655ea701b587bf1383c794f41b1aeb3bc32bca69ce3488852ec2c1474a2f47585608598b39ac05671490b8df63c5bc7d093f87e1afd5a92f908891b67 ``` 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.lock` byte-stability + b2sum stamp). ### Phase 3 status — DONE Ground truth locked: final-tree `5e65ccdc95828c118c88b07ecba41976ed631d0b`, cumulative b2sum `4d9d93c655ea701b…`. 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 --check` on baseline.ref).
Author
Owner

Phase 4 — design / implementation plan

CLI surface (one verb, four flags)

ka-promote <host>                          # resolve manifest, emit cumulative + lock
ka-promote <host> --output-dir <path>      # override default output dir
ka-promote <host> --validate-against <checkout>   # run git apply --check on a baseline checkout
ka-promote --list-hosts                    # list available fleet/*.yaml hosts
ka-promote --version                       # print ka-promote schema/algo version

Exit codes:

  • 0 success
  • 2 missing input (manifest, patch file)
  • 3 apply-check failed (only with --validate-against)
  • 4 manifest parse/schema error

No --force, no --dry-run, no --quiet. The verb is deterministic, fast, and read-mostly (only writes build/<host>/<ref>/). Re-running overwrites the output directory.

Resolver algorithm (single-file vs series-dir)

for each entry in manifest.includes (preserving list order):
    src = repo_root/patches/<entry>
    if entry ends with ".patch":
        # single-file include
        require src exists and is a regular file
        yield (entry, src, apply_order=next)
    elif entry ends with "/":
        # series-dir include
        require src exists and is a directory
        files = sorted(glob src/*.patch)
        files = [f for f in files if basename(f) != "0000-cover-letter.patch"]
        require len(files) > 0
        for f in files:
            yield (entry + basename(f), f, apply_order=next)
    else:
        error: include must end in ".patch" or "/"

The series-dir excludes 0000-cover-letter.patch because the cover letter is git format-patch metadata ([PATCH 0/7] ... subject line, no actual diff hunks). Per Phase-0 inventory, this is the universal convention in patches/driver/bes2600/*/.

Cumulative algorithm (= Phase 3 finding #2)

open cumulative.patch for writing
for (_, src, _) in resolved_patches:
    append entire contents of src to cumulative.patch
close
b2sum_hex = blake2b(cumulative.patch).hexdigest()

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

${KA_BUILD_DIR:-./build}/<host>/<baseline_ref>/
├── cumulative.patch       # all includes concatenated in apply order
└── manifest.lock          # YAML, machine + human readable

v1 does NOT emit flat per-patch files. ka-build (separate issue, follow-up) consumes cumulative.patch + b2sum-from-lock only. Flat output deferred to a --emit-flat flag in v2 if a PKGBUILD shape demands it; today every target consumer wants cumulative.

build/ added to .gitignore at repo root in the same PR as bin/ka-promote.

manifest.lock schema

ka_promote_version: 1
generated_at: 2026-05-18T10:07:43+02:00
host: fresnel
manifest:
  path: fleet/fresnel.yaml
  sha256: <of the source manifest file>
baseline:
  tree: torvalds/linux           # echoed from manifest, NOT validated by URL
  ref: v7.0                      # echoed from manifest, NOT validated by remote
resolved_patches:
  - apply_order: 1
    include: board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch
    sha256: e5a89ed6e4786567df65586da2d4f3ebbd659e9157ec83fdb9e460f5e7175c75
    size: 4127
    from_series: false
  - apply_order: 2
    ...
cumulative:
  path: cumulative.patch
  size: 27038
  b2sum: 4d9d93c655ea701b587bf1383c794f41b1aeb3bc32bca69ce3488852ec2c1474a2f47585608598b39ac05671490b8df63c5bc7d093f87e1afd5a92f908891b67

Determinism contract: given byte-identical inputs (manifest + all referenced .patch files), manifest.lock is byte-identical across runs. generated_at is the lone non-deterministic field; can be overridden via SOURCE_DATE_EPOCH env (reproducible-builds convention) if a downstream consumer needs perfect bit-stability. Default: real timestamp for human-readability.

Validator (--validate-against)

ka-promote fresnel --validate-against /path/to/linux-checkout

Behavior:

  1. cd /path/to/linux-checkout && git rev-parse HEAD^{tree} — capture current tree
  2. Look up git rev-parse <baseline.ref> — verify the ref exists and points at the same commit/tree as HEAD (warn if they diverge, do not fail)
  3. git apply --check <output>/cumulative.patch — fail (exit 3) if apply would not be clean
  4. No actual apply, no commit, no side-effect on the checkout

Validator is optional — the verb succeeds without it. --validate-against is 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-status precedent) that shells out to a small python3 helper 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)

  1. fresnel parity — replays Phase 3 baseline: ka-promote fresnel then diff cumulative b2sum against the recorded 4d9d93c655ea701b…. PASS = exact match.
  2. ampere parity — multi-scope smoke (per Phase 2): ka-promote ampere then diff against a Phase-3-style re-derived baseline for ampere (done as part of Phase 7 verify, not Phase 6).
  3. Series-dir resolver fixturetests/ka-promote/fixtures/series-dir.yaml containing one include driver/bes2600/staging-prep-series-danctnix/. Expected output: 6 resolved patches in filename order (0001-… through 0006-…), 0000-cover-letter.patch excluded. Pure resolver check, no apply.
  4. Bad-include rejection — manifest with include not ending in .patch or / exits 4.
  5. Missing patch rejection — manifest with include pointing at non-existent file exits 2.

Side-deliverable: fix Phase-3 finding #1

In the same PR, fix fleet/fresnel.yaml:

 baseline:
-  tree: mmind/linux-rockchip
-  url: https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git
+  tree: torvalds/linux
+  url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
   ref: v7.0
   upstream_compat: linux-7.0
+  patch_authoring_context: mmind/linux-rockchip   # informational: patches target Rockchip rebase context

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-rc3 correctly. ohm uses DanctNIX/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 4 — design / implementation plan ### CLI surface (one verb, four flags) ``` ka-promote <host> # resolve manifest, emit cumulative + lock ka-promote <host> --output-dir <path> # override default output dir ka-promote <host> --validate-against <checkout> # run git apply --check on a baseline checkout ka-promote --list-hosts # list available fleet/*.yaml hosts ka-promote --version # print ka-promote schema/algo version ``` Exit codes: - `0` success - `2` missing input (manifest, patch file) - `3` apply-check failed (only with `--validate-against`) - `4` manifest parse/schema error No `--force`, no `--dry-run`, no `--quiet`. The verb is deterministic, fast, and read-mostly (only writes `build/<host>/<ref>/`). Re-running overwrites the output directory. ### Resolver algorithm (single-file vs series-dir) ``` for each entry in manifest.includes (preserving list order): src = repo_root/patches/<entry> if entry ends with ".patch": # single-file include require src exists and is a regular file yield (entry, src, apply_order=next) elif entry ends with "/": # series-dir include require src exists and is a directory files = sorted(glob src/*.patch) files = [f for f in files if basename(f) != "0000-cover-letter.patch"] require len(files) > 0 for f in files: yield (entry + basename(f), f, apply_order=next) else: error: include must end in ".patch" or "/" ``` The series-dir excludes `0000-cover-letter.patch` because the cover letter is `git format-patch` metadata (`[PATCH 0/7] ...` subject line, no actual diff hunks). Per Phase-0 inventory, this is the universal convention in `patches/driver/bes2600/*/`. ### Cumulative algorithm (= Phase 3 finding #2) ``` open cumulative.patch for writing for (_, src, _) in resolved_patches: append entire contents of src to cumulative.patch close b2sum_hex = blake2b(cumulative.patch).hexdigest() ``` 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 ``` ${KA_BUILD_DIR:-./build}/<host>/<baseline_ref>/ ├── cumulative.patch # all includes concatenated in apply order └── manifest.lock # YAML, machine + human readable ``` **v1 does NOT emit flat per-patch files.** ka-build (separate issue, follow-up) consumes `cumulative.patch + b2sum-from-lock` only. Flat output deferred to a `--emit-flat` flag in v2 if a PKGBUILD shape demands it; today every target consumer wants cumulative. `build/` added to `.gitignore` at repo root in the same PR as `bin/ka-promote`. ### manifest.lock schema ```yaml ka_promote_version: 1 generated_at: 2026-05-18T10:07:43+02:00 host: fresnel manifest: path: fleet/fresnel.yaml sha256: <of the source manifest file> baseline: tree: torvalds/linux # echoed from manifest, NOT validated by URL ref: v7.0 # echoed from manifest, NOT validated by remote resolved_patches: - apply_order: 1 include: board/pinebook-pro/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch sha256: e5a89ed6e4786567df65586da2d4f3ebbd659e9157ec83fdb9e460f5e7175c75 size: 4127 from_series: false - apply_order: 2 ... cumulative: path: cumulative.patch size: 27038 b2sum: 4d9d93c655ea701b587bf1383c794f41b1aeb3bc32bca69ce3488852ec2c1474a2f47585608598b39ac05671490b8df63c5bc7d093f87e1afd5a92f908891b67 ``` Determinism contract: given byte-identical inputs (manifest + all referenced .patch files), `manifest.lock` is byte-identical across runs. `generated_at` is the lone non-deterministic field; can be overridden via `SOURCE_DATE_EPOCH` env (reproducible-builds convention) if a downstream consumer needs perfect bit-stability. Default: real timestamp for human-readability. ### Validator (--validate-against) ``` ka-promote fresnel --validate-against /path/to/linux-checkout ``` Behavior: 1. `cd /path/to/linux-checkout && git rev-parse HEAD^{tree}` — capture current tree 2. Look up `git rev-parse <baseline.ref>` — verify the ref exists and points at the same commit/tree as HEAD (warn if they diverge, do not fail) 3. `git apply --check <output>/cumulative.patch` — fail (exit 3) if apply would not be clean 4. No actual apply, no commit, no side-effect on the checkout Validator is **optional** — the verb succeeds without it. `--validate-against` is 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-status` precedent) that shells out to a small `python3` helper 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) 1. **fresnel parity** — replays Phase 3 baseline: `ka-promote fresnel` then diff cumulative b2sum against the recorded `4d9d93c655ea701b…`. PASS = exact match. 2. **ampere parity** — multi-scope smoke (per Phase 2): `ka-promote ampere` then diff against a Phase-3-style re-derived baseline for ampere (done as part of Phase 7 verify, not Phase 6). 3. **Series-dir resolver fixture** — `tests/ka-promote/fixtures/series-dir.yaml` containing one include `driver/bes2600/staging-prep-series-danctnix/`. Expected output: 6 resolved patches in filename order (`0001-…` through `0006-…`), `0000-cover-letter.patch` excluded. Pure resolver check, no apply. 4. **Bad-include rejection** — manifest with include not ending in `.patch` or `/` exits 4. 5. **Missing patch rejection** — manifest with include pointing at non-existent file exits 2. ### Side-deliverable: fix Phase-3 finding #1 In the same PR, fix `fleet/fresnel.yaml`: ```diff baseline: - tree: mmind/linux-rockchip - url: https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git + tree: torvalds/linux + url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ref: v7.0 upstream_compat: linux-7.0 + patch_authoring_context: mmind/linux-rockchip # informational: patches target Rockchip rebase context ``` 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-rc3` correctly. ohm uses `DanctNIX/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.
Author
Owner

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-danctnix has 7 patches, not 6 — cover-letter filter mask is fine, but the series-dir test fixture count is wrong in the plan.

0000-cover-letter.patch   ← excluded
0001- … 0007-            ← 7 applied patches

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.md files inside series-dirs are not excluded by the current filter.

The glob *.patch already excludes them (README.md doesn't match), so staging-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 *.patch glob is the one used, not * with a name-filter. The plan uses glob src/*.patch which is correct. Noting this so the implementer doesn't widen the glob later without knowing the invariant.

3. --validate-against step 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:

if [[ "$actual_tree" != "$(git rev-parse ${baseline_ref}^{tree})" ]]; then
    if [[ -n "$CI" ]]; then exit 3; fi  # hard fail in CI
    warn "checkout does not match baseline.ref — apply-check result is unreliable"
fi

Or document "only pass --validate-against on a clean checkout of baseline.ref" and make divergence a hard error always. Either is fine; current "warn, proceed" is a trap.

4. Determinism: manifest.sha256 is 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 includes entries are silently applied twice.

The resolver has no dedup check. git apply will fail mid-way ("already applied" or hunk reject) with an unhelpful error. Worth a pre-flight check:

seen = set()
for entry in includes:
    if entry in seen:
        die(f"duplicate include: {entry}", exit=4)
    seen.add(entry)

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

  • Cover-letter exclusion scope: the 0000- filter is justified by two independent series dirs (staging-prep-series and staging-prep-series-danctnix), both using the same convention. Not a single-data-point risk.
  • Non-cover-letter metadata files in series-dirs: *.patch glob handles this structurally. README.md, MAINTAINERS, etc. never match.
  • CRLF patches: git apply normalises line endings. Not a ka-promote concern.
  • generated_at as sole non-determinism: confirmed. Python yaml.dump with default_flow_style=False and an explicit sort_keys=True (or sorted dict construction) is stable across runs. If PyYAML's default sort_keys=True is relied on, note that in a code comment so nobody removes it.
  • --validate-against using a path not remote+ref: correct call. Requiring a local checkout keeps the verb offline-capable and fast. The setup cost (one git worktree add) is documented in Phase 3 and is mechanical.
  • Ampere include list numbering gap (0001/2/3/4/5/8): not a ka-promote concern — manifest list-order is apply-order, file numbering is ignored by the resolver.
  • build/ gitignore: straightforward, not a gap.
  • v1 drops flat per-patch output: correct deferral. No current PKGBUILD consumer needs it.

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:

  • Fix series-dir fixture expected count: 7 patches, not 6
  • --validate-against divergence = hard error (or at minimum documented hard-error in CI)
  • Hash manifest bytes raw, not parsed
  • Duplicate-include pre-flight check (4 lines, exit 4)
  • yaml.dump(..., sort_keys=True) explicit in the lock-writer (don't leave it implicit)
## 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-danctnix` has 7 patches, not 6 — cover-letter filter mask is fine, but the series-dir test fixture count is wrong in the plan.** ``` 0000-cover-letter.patch ← excluded 0001- … 0007- ← 7 applied patches ``` 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.md` files inside series-dirs are not excluded by the current filter.** The glob `*.patch` already excludes them (README.md doesn't match), so `staging-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 `*.patch` glob is the one used, not `*` with a name-filter. The plan uses `glob src/*.patch` which is correct. Noting this so the implementer doesn't widen the glob later without knowing the invariant. **3. `--validate-against` step 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: ``` if [[ "$actual_tree" != "$(git rev-parse ${baseline_ref}^{tree})" ]]; then if [[ -n "$CI" ]]; then exit 3; fi # hard fail in CI warn "checkout does not match baseline.ref — apply-check result is unreliable" fi ``` Or document "only pass `--validate-against` on a clean checkout of `baseline.ref`" and make divergence a hard error always. Either is fine; current "warn, proceed" is a trap. **4. Determinism: `manifest.sha256` is 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 `includes` entries are silently applied twice.** The resolver has no dedup check. `git apply` will fail mid-way ("already applied" or hunk reject) with an unhelpful error. Worth a pre-flight check: ```python seen = set() for entry in includes: if entry in seen: die(f"duplicate include: {entry}", exit=4) seen.add(entry) ``` 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 - **Cover-letter exclusion scope**: the `0000-` filter is justified by two independent series dirs (`staging-prep-series` and `staging-prep-series-danctnix`), both using the same convention. Not a single-data-point risk. - **Non-cover-letter metadata files in series-dirs**: `*.patch` glob handles this structurally. README.md, MAINTAINERS, etc. never match. - **CRLF patches**: `git apply` normalises line endings. Not a ka-promote concern. - **`generated_at` as sole non-determinism**: confirmed. Python `yaml.dump` with `default_flow_style=False` and an explicit `sort_keys=True` (or sorted dict construction) is stable across runs. If PyYAML's default `sort_keys=True` is relied on, note that in a code comment so nobody removes it. - **`--validate-against` using a path not remote+ref**: correct call. Requiring a local checkout keeps the verb offline-capable and fast. The setup cost (one `git worktree add`) is documented in Phase 3 and is mechanical. - **Ampere include list numbering gap (0001/2/3/4/5/8)**: not a ka-promote concern — manifest list-order is apply-order, file numbering is ignored by the resolver. - **`build/` gitignore**: straightforward, not a gap. - **v1 drops flat per-patch output**: correct deferral. No current PKGBUILD consumer needs it. ### 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: - [ ] Fix series-dir fixture expected count: 7 patches, not 6 - [ ] `--validate-against` divergence = hard error (or at minimum documented hard-error in CI) - [ ] Hash manifest bytes raw, not parsed - [ ] Duplicate-include pre-flight check (4 lines, exit 4) - [ ] `yaml.dump(..., sort_keys=True)` explicit in the lock-writer (don't leave it implicit)
Author
Owner

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.yaml resolves to 7 patches, not 6. Updating implementation TODO list accordingly.

Root cause: ls -la | head -10 in 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 -l would have shown 8.

## 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.yaml` resolves to **7** patches, not 6. Updating implementation TODO list accordingly. Root cause: `ls -la | head -10` in 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 -l` would have shown 8.
Author
Owner

Phase 6 — implement: done, committed, pushed

Branch: claude-noether/kernel-agent:noether/ka-promote (commit beadf24). 500 insertions / 6 deletions across 8 files.

Files

File LoC Purpose
bin/ka-promote 264 resolver + cumulative + manifest.lock writer + validator
tests/ka-promote/run-tests.sh 138 5-test runner with sandboxed fleet/ + patches/ symlink
tests/ka-promote/fixtures/series-dir.yaml 21 bes2600/staging-prep-series-danctnix → 7 patches
tests/ka-promote/fixtures/bad-include.yaml 20 entry not ending in .patch or / → exit 4
tests/ka-promote/fixtures/missing-patch.yaml 20 nonexistent .patch → exit 2
tests/ka-promote/fixtures/duplicate-include.yaml 21 same include twice → exit 4
.gitignore 7 build/ + python cruft
fleet/fresnel.yaml ±15 baseline.tree mmind → torvalds (Phase-3 finding #1)

Test results (noether)

PASS  fresnel parity (= Phase-3 ground truth b2sum)
PASS  series-dir resolver (bes2600/staging-prep-series-danctnix → 7 patches)
PASS  bad-include rejection (exit 4)
PASS  missing-patch rejection (exit 2)
PASS  duplicate-include rejection (exit 4)
passed: 5
failed: 0

Validator smoke (boltzmann, against ~/src/kernel-agent-bootstrap/ka-promote-phase3/linux-v7.0)

Scenario Result
Clean v7.0 worktree exit 0, b2sum matches Phase-3 ground truth bit-for-bit
Dirty worktree (uncommitted change) exit 3, "checkout has uncommitted changes"
Tree-divergent HEAD (real content commit on top) exit 3, "HEAD tree X != baseline.ref tree Y"
Empty commit on top (HEAD differs, tree identical) exit 0 (correct — patches apply equally)

Phase-5 followups status

  • Series-dir fixture count: 7 (not 6)
  • --validate-against divergence = hard error (plus added dirty-tree hard error caught during smoke testing)
  • Hash manifest raw bytes, not parsed (hashlib.sha256(raw).hexdigest())
  • Duplicate-include pre-flight check (exit 4)
  • yaml.dump(..., sort_keys=True) explicit

Language 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-packages kernel-agent package dependency list if/when one exists.

Phase 7 next

Verify ampere parity. Plan:

  1. Worktree torvalds v7.0-rc3 on boltzmann (already exists in linux-mainline).
  2. Phase-3-style hand-apply of the 7 ampere patches in manifest list order.
  3. ka-promote ampere against that worktree with --validate-against.
  4. Diff b2sum vs hand-derived. PASS = parity confirmed on multi-scope manifest.

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 6 — implement: done, committed, pushed Branch: `claude-noether/kernel-agent:noether/ka-promote` (commit `beadf24`). 500 insertions / 6 deletions across 8 files. ### Files | File | LoC | Purpose | |---|---|---| | `bin/ka-promote` | 264 | resolver + cumulative + manifest.lock writer + validator | | `tests/ka-promote/run-tests.sh` | 138 | 5-test runner with sandboxed fleet/ + patches/ symlink | | `tests/ka-promote/fixtures/series-dir.yaml` | 21 | bes2600/staging-prep-series-danctnix → 7 patches | | `tests/ka-promote/fixtures/bad-include.yaml` | 20 | entry not ending in .patch or / → exit 4 | | `tests/ka-promote/fixtures/missing-patch.yaml` | 20 | nonexistent .patch → exit 2 | | `tests/ka-promote/fixtures/duplicate-include.yaml` | 21 | same include twice → exit 4 | | `.gitignore` | 7 | build/ + python cruft | | `fleet/fresnel.yaml` | ±15 | baseline.tree mmind → torvalds (Phase-3 finding #1) | ### Test results (noether) ``` PASS fresnel parity (= Phase-3 ground truth b2sum) PASS series-dir resolver (bes2600/staging-prep-series-danctnix → 7 patches) PASS bad-include rejection (exit 4) PASS missing-patch rejection (exit 2) PASS duplicate-include rejection (exit 4) passed: 5 failed: 0 ``` ### Validator smoke (boltzmann, against `~/src/kernel-agent-bootstrap/ka-promote-phase3/linux-v7.0`) | Scenario | Result | |---|---| | Clean `v7.0` worktree | exit 0, b2sum matches Phase-3 ground truth bit-for-bit | | Dirty worktree (uncommitted change) | exit 3, "checkout has uncommitted changes" | | Tree-divergent HEAD (real content commit on top) | exit 3, "HEAD tree X != baseline.ref tree Y" | | Empty commit on top (HEAD differs, tree identical) | exit 0 (correct — patches apply equally) | ### Phase-5 followups status - [x] Series-dir fixture count: 7 (not 6) - [x] `--validate-against` divergence = hard error (plus added dirty-tree hard error caught during smoke testing) - [x] Hash manifest raw bytes, not parsed (`hashlib.sha256(raw).hexdigest()`) - [x] Duplicate-include pre-flight check (exit 4) - [x] `yaml.dump(..., sort_keys=True)` explicit ### Language 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-packages` `kernel-agent` package dependency list if/when one exists. ### Phase 7 next Verify ampere parity. Plan: 1. Worktree torvalds v7.0-rc3 on boltzmann (already exists in linux-mainline). 2. Phase-3-style hand-apply of the 7 ampere patches in manifest list order. 3. `ka-promote ampere` against that worktree with `--validate-against`. 4. Diff b2sum vs hand-derived. PASS = parity confirmed on multi-scope manifest. 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.
Author
Owner

Phase 7 — verify: ampere multi-scope parity PASS

Setup

  • Substrate: torvalds/linux v7.0-rc3 (commit 3daa4f5dc6cc) worktree at boltzmann:~/src/kernel-agent-bootstrap/ka-promote-phase3/linux-v7.0-rc3
  • Branch rebased onto origin/main to 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 (91fe815 replaces beadf24).
  • 7 includes across 4 scopes: soc/rockchip/rk3588/ ×1, board/coolpi-cm5-genbook/ ×5, module/coolpi-cm5/ ×1.

Three-way comparison

iterative-apply ground-truth tree:  c29bef57e135b2bc548bdc4a5e298e6d7200c546
hand-cat cumulative tree:           c29bef57e135b2bc548bdc4a5e298e6d7200c546  ✓
ka-promote ampere cumulative tree:  c29bef57e135b2bc548bdc4a5e298e6d7200c546  ✓

hand-cat cumulative b2sum:       476d9507218ca1b8e3aebe5076fb33942abb7aed99468cfe44888a918ea24bb72d45c72280da94d6ee78158a84330dbc1f785c88c904cd480a1c4f0cb9cddf6e
ka-promote ampere b2sum:         476d9507218ca1b8e3aebe5076fb33942abb7aed99468cfe44888a918ea24bb72d45c72280da94d6ee78158a84330dbc1f785c88c904cd480a1c4f0cb9cddf6e   ✓

ka-promote ampere --validate-against <worktree>:  exit 0

Confirmed

  • Multi-scope resolver (3 different scope dirs in one manifest) works identically to flat-scope.
  • Apply order = manifest list order, even when filename numbers skip (0001/2/3/4/5/8/11).
  • Deterministic byte-equal output across hand-cat and ka-promote.
  • --validate-against on a clean checkout of baseline.ref exits 0.

Whitespace observation (non-blocking)

patches/board/coolpi-cm5-genbook/0011-…wire-internal-microphone.patch:63 has a new blank line at EOF whitespace warning when git apply runs. Patch still applies cleanly. Not a ka-promote concern; if it bothers downstream consumers, the fix is to re-export the patch via git format-patch after 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)

PASS  fresnel parity (= Phase-3 ground truth b2sum)
PASS  series-dir resolver (bes2600/staging-prep-series-danctnix → 7 patches)
PASS  bad-include rejection (exit 4)
PASS  missing-patch rejection (exit 2)
PASS  duplicate-include rejection (exit 4)
passed: 5/5

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-promotemarfrit/kernel-agent:main, close this issue on merge.

## Phase 7 — verify: ampere multi-scope parity PASS ### Setup - Substrate: `torvalds/linux v7.0-rc3` (commit `3daa4f5dc6cc`) worktree at `boltzmann:~/src/kernel-agent-bootstrap/ka-promote-phase3/linux-v7.0-rc3` - Branch rebased onto `origin/main` to 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** (`91fe815` replaces `beadf24`). - 7 includes across 4 scopes: `soc/rockchip/rk3588/` ×1, `board/coolpi-cm5-genbook/` ×5, `module/coolpi-cm5/` ×1. ### Three-way comparison ``` iterative-apply ground-truth tree: c29bef57e135b2bc548bdc4a5e298e6d7200c546 hand-cat cumulative tree: c29bef57e135b2bc548bdc4a5e298e6d7200c546 ✓ ka-promote ampere cumulative tree: c29bef57e135b2bc548bdc4a5e298e6d7200c546 ✓ hand-cat cumulative b2sum: 476d9507218ca1b8e3aebe5076fb33942abb7aed99468cfe44888a918ea24bb72d45c72280da94d6ee78158a84330dbc1f785c88c904cd480a1c4f0cb9cddf6e ka-promote ampere b2sum: 476d9507218ca1b8e3aebe5076fb33942abb7aed99468cfe44888a918ea24bb72d45c72280da94d6ee78158a84330dbc1f785c88c904cd480a1c4f0cb9cddf6e ✓ ka-promote ampere --validate-against <worktree>: exit 0 ``` ### Confirmed - Multi-scope resolver (3 different scope dirs in one manifest) works identically to flat-scope. - Apply order = manifest list order, even when filename numbers skip (0001/2/3/4/5/8/11). - Deterministic byte-equal output across hand-cat and ka-promote. - `--validate-against` on a clean checkout of `baseline.ref` exits 0. ### Whitespace observation (non-blocking) `patches/board/coolpi-cm5-genbook/0011-…wire-internal-microphone.patch:63` has a `new blank line at EOF` whitespace warning when `git apply` runs. Patch still applies cleanly. Not a ka-promote concern; if it bothers downstream consumers, the fix is to re-export the patch via `git format-patch` after 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) ``` PASS fresnel parity (= Phase-3 ground truth b2sum) PASS series-dir resolver (bes2600/staging-prep-series-danctnix → 7 patches) PASS bad-include rejection (exit 4) PASS missing-patch rejection (exit 2) PASS duplicate-include rejection (exit 4) passed: 5/5 ``` ### 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.
Author
Owner

Phase 8 — close: PR opened, README rewritten

PR: marfrit/kernel-agent#23claude-noether:noether/ka-promotemarfrit:main.

Final commit set on branch

Commit Scope
91fe815 bin/ka-promote + tests + .gitignore + fleet/fresnel.yaml fix (Phase 6)
3c6d356 README: ka-promote implemented + ka-import rename (Phase 8)

README changes

  • CLI verb table: ka-promote <host> marked [bin/ka-promote — implemented Phase 6, issue #22]. ka-build / ka-install get explicit "next/last verb to implement" markers.
  • Original ka-promote <campaign> <patch-or-glob> --to <scope> semantic renamed to ka-import (still unimplemented; today via manual git+PR workflow). Note paragraph added explaining the rename.
  • "Bootstrap reference build" section: header reframed (verbs partially exist now), Inputs.Baseline corrected (torvalds, not mmind), Manual-substitute table gets a Status column with the ka-promote row 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.

## Phase 8 — close: PR opened, README rewritten PR: marfrit/kernel-agent#23 — `claude-noether:noether/ka-promote` → `marfrit:main`. ### Final commit set on branch | Commit | Scope | |---|---| | `91fe815` | bin/ka-promote + tests + .gitignore + fleet/fresnel.yaml fix (Phase 6) | | `3c6d356` | README: ka-promote implemented + ka-import rename (Phase 8) | ### README changes - CLI verb table: `ka-promote <host>` marked `[bin/ka-promote — implemented Phase 6, issue #22]`. `ka-build` / `ka-install` get explicit "next/last verb to implement" markers. - Original `ka-promote <campaign> <patch-or-glob> --to <scope>` semantic renamed to `ka-import` (still unimplemented; today via manual git+PR workflow). Note paragraph added explaining the rename. - "Bootstrap reference build" section: header reframed (verbs partially exist now), Inputs.Baseline corrected (torvalds, not mmind), Manual-substitute table gets a Status column with the `ka-promote` row 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.
Sign in to join this conversation.