From 70873396a5f7e1e79fed0358fac46e6b83241a15 Mon Sep 17 00:00:00 2001 From: Asgeir Frimannsson Date: Tue, 15 Sep 2026 08:28:34 +0200 Subject: [PATCH 1/4] test: round-trip the state cache on a kapi 1.2 project Adds a fixture project and two jobs: the first runs it and lets the action save its cache, the second restores the cache and requires a warm run to match a cold run of a pristine copy in every exit code, output and written file. A negative control restores .kapi/work/store.db and requires the comparison to report a difference. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VRid8i4qnuNfE7Lio73Uu4 --- .github/workflows/test.yml | 130 ++++++++++++++++++++++++++++++ test/fixture/.kapi/.gitignore | 2 + test/fixture/docs/en/intro.md | 5 ++ test/fixture/kapi.yaml | 20 +++++ test/fixture/locales/en.json | 6 ++ test/state-cache/change-source.sh | 16 ++++ test/state-cache/cold-copy.sh | 9 +++ test/state-cache/compare.sh | 20 +++++ test/state-cache/run.sh | 49 +++++++++++ 9 files changed, 257 insertions(+) create mode 100644 test/fixture/.kapi/.gitignore create mode 100644 test/fixture/docs/en/intro.md create mode 100644 test/fixture/kapi.yaml create mode 100644 test/fixture/locales/en.json create mode 100755 test/state-cache/change-source.sh create mode 100755 test/state-cache/cold-copy.sh create mode 100755 test/state-cache/compare.sh create mode 100755 test/state-cache/run.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c7ec3d..7c331e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,11 @@ on: - cron: "17 5 * * *" workflow_dispatch: +env: + # The state cache jobs need kapi 1.2, the first release that keeps its state + # under .kapi/work/. + STATE_CACHE_KAPI_VERSION: 1.2.0-rc32 + jobs: test-latest: name: "Latest (${{ matrix.os }})" @@ -130,6 +135,131 @@ jobs: shell: bash run: kapi version + # The state cache round trip on a kapi 1.2 project. This job runs the fixture + # and the action saves the cache at job end; the next job restores it. Each + # job compares its run in the cached project directory with a cold run of a + # pristine copy, and the comparison covers every command's exit code and + # output and every file the project holds afterwards. + test-state-cache-save: + name: "State cache save (${{ matrix.os }})" + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # kapi 1.2 prereleases publish no Windows CLI build. + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v6 + + - name: Setup kapi + uses: ./ + with: + token: ${{ secrets.NEOKAPI_GITHUB_TOKEN }} + version: ${{ env.STATE_CACHE_KAPI_VERSION }} + plugins: "" + project-dir: test/fixture + + - name: Run the fixture and a cold copy + shell: bash + run: | + test/state-cache/cold-copy.sh "${RUNNER_TEMP}/cold" + test/state-cache/run.sh test/fixture "${RUNNER_TEMP}/results/cached" + test/state-cache/run.sh "${RUNNER_TEMP}/cold/test/fixture" "${RUNNER_TEMP}/results/cold" + test/state-cache/compare.sh "${RUNNER_TEMP}/results/cold" "${RUNNER_TEMP}/results/cached" + + # The restore job needs something to restore; this fails first if kapi + # stops writing the path the action caches. + - name: Require the parse cache kapi writes + shell: bash + run: | + test -s test/fixture/.kapi/work/cache/docs/index.db + cp test/fixture/.kapi/work/store.db "${RUNNER_TEMP}/results/store.db" + + - uses: actions/upload-artifact@v7 + with: + name: state-cache-save-${{ matrix.os }} + path: ${{ runner.temp }}/results + + test-state-cache-restore: + name: "State cache restore (${{ matrix.os }})" + needs: test-state-cache-save + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v6 + + - name: Setup kapi + uses: ./ + with: + token: ${{ secrets.NEOKAPI_GITHUB_TOKEN }} + version: ${{ env.STATE_CACHE_KAPI_VERSION }} + plugins: "" + project-dir: test/fixture + + - name: Require the restored parse cache + shell: bash + run: | + docs=test/fixture/.kapi/work/cache/docs + test -s "${docs}/index.db" + ls "${docs}"/parts-*.log > /dev/null + echo "Restored $(find "${docs}" -type f | wc -l | tr -d ' ') files into ${docs}" + cp -R "${docs}" "${RUNNER_TEMP}/restored-docs" + + - uses: actions/download-artifact@v8 + with: + name: state-cache-save-${{ matrix.os }} + path: ${{ runner.temp }}/saved + + - name: A warm run matches a cold run + shell: bash + run: | + test/state-cache/cold-copy.sh "${RUNNER_TEMP}/cold" + test/state-cache/run.sh test/fixture "${RUNNER_TEMP}/results/warm" + test/state-cache/run.sh "${RUNNER_TEMP}/cold/test/fixture" "${RUNNER_TEMP}/results/cold" + test -s "${RUNNER_TEMP}/results/warm/docs-before.txt" + test/state-cache/compare.sh "${RUNNER_TEMP}/results/cold" "${RUNNER_TEMP}/results/warm" + test/state-cache/compare.sh "${RUNNER_TEMP}/saved/cold" "${RUNNER_TEMP}/results/warm" + + - name: A cache saved before a source change does not hide it + shell: bash + run: | + git clean -fdxq test/fixture + git checkout -- test/fixture + mkdir -p test/fixture/.kapi/work/cache + cp -R "${RUNNER_TEMP}/restored-docs" test/fixture/.kapi/work/cache/docs + test/state-cache/change-source.sh test/fixture + test/state-cache/cold-copy.sh "${RUNNER_TEMP}/cold-changed" + test/state-cache/change-source.sh "${RUNNER_TEMP}/cold-changed/test/fixture" + test/state-cache/run.sh test/fixture "${RUNNER_TEMP}/results/warm-changed" + test/state-cache/run.sh "${RUNNER_TEMP}/cold-changed/test/fixture" "${RUNNER_TEMP}/results/cold-changed" + test/state-cache/compare.sh "${RUNNER_TEMP}/results/cold-changed" "${RUNNER_TEMP}/results/warm-changed" + + # The negative control: .kapi/work/store.db holds stored targets and the + # unit working set, so restoring it has to change a result. If the + # comparison reports no difference here, it cannot be trusted above. + - name: The comparison catches a restored store + shell: bash + run: | + git clean -fdxq test/fixture + git checkout -- test/fixture + mkdir -p test/fixture/.kapi/work/cache + cp -R "${RUNNER_TEMP}/restored-docs" test/fixture/.kapi/work/cache/docs + cp "${RUNNER_TEMP}/saved/store.db" test/fixture/.kapi/work/store.db + test/state-cache/run.sh test/fixture "${RUNNER_TEMP}/results/warm-store" + if test/state-cache/compare.sh "${RUNNER_TEMP}/results/cold" "${RUNNER_TEMP}/results/warm-store"; then + echo "::error::Restoring .kapi/work/store.db changed no result, so the comparison cannot detect restored state." + exit 1 + fi + + - uses: actions/upload-artifact@v7 + if: always() + with: + name: state-cache-restore-${{ matrix.os }} + path: ${{ runner.temp }}/results + test-cache: name: "Cache hit (${{ matrix.os }})" runs-on: ${{ matrix.os }} diff --git a/test/fixture/.kapi/.gitignore b/test/fixture/.kapi/.gitignore new file mode 100644 index 0000000..7e05f20 --- /dev/null +++ b/test/fixture/.kapi/.gitignore @@ -0,0 +1,2 @@ +work/ +filters.local.json diff --git a/test/fixture/docs/en/intro.md b/test/fixture/docs/en/intro.md new file mode 100644 index 0000000..af046f2 --- /dev/null +++ b/test/fixture/docs/en/intro.md @@ -0,0 +1,5 @@ +# Getting started + +This guide shows how to install the tool and run it the first time. + +Run the installer, then open a terminal and type the command. diff --git a/test/fixture/kapi.yaml b/test/fixture/kapi.yaml new file mode 100644 index 0000000..25f2ba5 --- /dev/null +++ b/test/fixture/kapi.yaml @@ -0,0 +1,20 @@ +# A small kapi 1.2 project for the state cache test. The pseudo flow needs no +# provider credentials, so kapi up runs the same on every runner. +version: v1 +name: cache-fixture +defaults: + source_language: en + target_languages: + - fr + flow: pseudo +collections: + - path: "locales/en.json" + format: json + target: "locales/{lang}.json" + - path: "docs/en/*.md" + format: markdown + target: "docs/{lang}/*.md" +flows: + pseudo: + steps: + - tool: pseudo-translate diff --git a/test/fixture/locales/en.json b/test/fixture/locales/en.json new file mode 100644 index 0000000..279bfe9 --- /dev/null +++ b/test/fixture/locales/en.json @@ -0,0 +1,6 @@ +{ + "greeting": "Hello, {name}!", + "farewell": "Goodbye and good luck.", + "cta": "Start your free trial today", + "items": "You have {count} items in your cart" +} diff --git a/test/state-cache/change-source.sh b/test/state-cache/change-source.sh new file mode 100755 index 0000000..f950aa5 --- /dev/null +++ b/test/state-cache/change-source.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Edits the fixture's JSON source the way a later commit would: one string +# changes, a doubled space is fixed and a string is added. The Markdown source +# is left alone, so a restored parse cache still holds a valid entry for it. +# +# usage: change-source.sh +set -euo pipefail +cat > "$1/locales/en.json" << 'JSON' +{ + "greeting": "Welcome back, {name}!", + "farewell": "Goodbye and good luck.", + "cta": "Start your free trial today", + "items": "You have {count} items in your cart", + "empty": "Your cart is empty" +} +JSON diff --git a/test/state-cache/cold-copy.sh b/test/state-cache/cold-copy.sh new file mode 100755 index 0000000..cf9ec31 --- /dev/null +++ b/test/state-cache/cold-copy.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Extracts the committed fixture into a fresh directory, so a cold run sees +# exactly what a checkout holds and no state from any earlier run. +# +# usage: cold-copy.sh (the project lands in /test/fixture) +set -euo pipefail +rm -rf "$1" +mkdir -p "$1" +git archive HEAD test/fixture | tar -x -C "$1" diff --git a/test/state-cache/compare.sh b/test/state-cache/compare.sh new file mode 100755 index 0000000..85f4dce --- /dev/null +++ b/test/state-cache/compare.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Compares two records written by run.sh: every command's exit code and +# normalized output, and the files the project held afterwards. Prints each +# difference and exits 1 when there is any. +# +# usage: compare.sh +set -uo pipefail +a=$1 +b=$2 +status=0 +for name in status-before check-before ship-before up status-after check-after ship-after; do + for ext in rc json; do + diff -u "$a/$name.$ext" "$b/$name.$ext" || status=1 + done +done +diff -u "$a/tree.txt" "$b/tree.txt" || status=1 +if [ "$status" -eq 0 ]; then + echo "Identical: $a and $b" +fi +exit "$status" diff --git a/test/state-cache/run.sh b/test/state-cache/run.sh new file mode 100755 index 0000000..c651ff1 --- /dev/null +++ b/test/state-cache/run.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Runs a fixed kapi command sequence over a project and records what each +# command reported and which files the project holds afterwards, normalized so +# that two runs can be compared with compare.sh. +# +# usage: run.sh +set -uo pipefail + +project=$(cd "$1" && pwd) || exit 1 +mkdir -p "$2" +record=$(cd "$2" && pwd) || exit 1 + +sha() { + if command -v sha256sum > /dev/null; then sha256sum | cut -d' ' -f1; else shasum -a 256 | cut -d' ' -f1; fi +} + +cd "$project" || exit 1 +find .kapi/work/cache/docs -type f 2> /dev/null | LC_ALL=C sort > "$record/docs-before.txt" + +# Durations and timings differ between runs, so they are removed before the +# output is recorded. Exit codes are recorded as they are. +strip='del(.. | .duration_ms?) | del(.. | .timings?)' +run() { + local name=$1 + shift + kapi "$@" > "$record/$name.raw" 2> "$record/$name.err" + echo $? > "$record/$name.rc" + jq -c "$strip" "$record/$name.raw" > "$record/$name.json" 2> /dev/null || cp "$record/$name.raw" "$record/$name.json" +} + +run status-before status -p kapi.yaml --json +run check-before check -p kapi.yaml --output-format json +run ship-before check --ship -p kapi.yaml --output-format json +run up up -p kapi.yaml --json +run status-after status -p kapi.yaml --json +run check-after check -p kapi.yaml --output-format json +run ship-after check --ship -p kapi.yaml --output-format json + +find .kapi/work/cache/docs -type f 2> /dev/null | LC_ALL=C sort > "$record/docs-after.txt" + +# Every file outside .kapi/work, hashed. The unit-state record stamps each unit +# with the time it was written, so that field is removed before hashing. +find . -type f -not -path './.kapi/work/*' | LC_ALL=C sort | while read -r f; do + case "$f" in + ./.kapi/state/*) h=$(sed -E 's/"updated":"[^"]*",?//g' "$f" | sha) ;; + *) h=$(sha < "$f") ;; + esac + echo "$h $f" +done > "$record/tree.txt" From c4c0ad84e994d1bccc42c080e79bc2342c49b5f8 Mon Sep 17 00:00:00 2001 From: Asgeir Frimannsson Date: Tue, 15 Sep 2026 08:31:49 +0200 Subject: [PATCH 2/4] fix: cache the parse cache kapi 1.2 writes, not retired paths The state cache cached .kapi/tm.db and .kapi/cache, which kapi 1.2 never writes, so it saved nothing. It now caches .kapi/work/cache/docs, the one path under .kapi/work that kapi keys by content, configuration, recipe and build. The store and the rest of .kapi/work stay out: restoring them changes what kapi status, kapi check --ship and kapi up report. The key carries the resolved kapi version, and the input keeps its name. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VRid8i4qnuNfE7Lio73Uu4 --- README.md | 26 +++++++++++++++++---- action.yml | 66 +++++++++++++++++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 976c9f0..4390ddd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ steps: version: "1.1.0" ``` -### Run a localization pipeline +### Run a flow ```yaml steps: @@ -42,12 +42,15 @@ needs credentials: steps: - uses: neokapi/setup-kapi@v1 with: + version: "1.2.0-rc32" auth-token: ${{ secrets.BOWRAIN_AUTH_TOKEN }} server: https://your.bowrain.server - run: kapi up ``` +The newest stable release, which `latest` installs, has no `kapi up`, so this example pins the prerelease that has it. + `kapi up` runs the kapi loop: with the bowrain plugin installed and a `server:` block in the recipe, it pushes, catches up on the server, and pulls the produced targets back. To run it and commit the results, pair this with [`kapi-action`](https://github.com/neokapi/kapi-action). ## Inputs @@ -59,8 +62,8 @@ steps: | `plugins` | Newline- or comma-separated plugin refs to install, as the registry names them (`bowrain`, `okapi-bridge`; a `kapi-` prefix is stripped). Pass `''` to install nothing | `bowrain` | No | | `auth-token` | Bowrain server JWT, exported as `BOWRAIN_AUTH_TOKEN` | — | No | | `server` | Bowrain server URL, exported as `BOWRAIN_SERVER_URL` | — | No | -| `cache-tm` | Restore/persist the project translation memory across runs via the job cache (out of git). Runs only when a `kapi.yaml` recipe (or legacy `*.kapi`) is present. Set `false` to disable | `true` | No | -| `project-dir` | Directory holding the `kapi.yaml` project for the TM cache | `.` | No | +| `cache-tm` | Carry kapi's parse cache (`.kapi/work/cache/docs`) between runs with the job cache; see [Project parse cache](#project-parse-cache). Runs only when a `kapi.yaml` recipe (or legacy `*.kapi`) is present. Set `false` to disable | `true` | No | +| `project-dir` | Directory holding the `kapi.yaml` project whose parse cache is carried between runs | `.` | No | ## Outputs @@ -102,12 +105,27 @@ image. 4. **Add to PATH** — makes `kapi` available to all subsequent steps. 5. **Configure auth** (optional) — exports `BOWRAIN_AUTH_TOKEN`/`BOWRAIN_SERVER_URL` when `auth-token` is set. 6. **Install plugins** — installs each ref in `plugins` (default: `bowrain`) via `kapi plugins install`, cached keyed on the plugin set + OS + arch. Refs use the registry names; a `kapi-` binary prefix is stripped (`kapi-bowrain` → `bowrain`). -7. **Restore project TM cache** (when a `kapi.yaml` recipe (or legacy `*.kapi`) is present) — restores the latest translation memory for the branch from the job cache and, via a run-unique key, saves the grown TM back at job end. The TM is **derived state kept out of git**: it accumulates leverage across runs without being committed, and a cold cache simply rebuilds from the committed translations. No commits, no locking (per-branch, last-write-wins). Disable with `cache-tm: false`. +7. **Restore the project parse cache** (when a `kapi.yaml` recipe, or legacy `*.kapi`, is present): restores `.kapi/work/cache/docs` from the job cache, and saves it again at job end under a key unique to the job and run attempt. See [Project parse cache](#project-parse-cache). Disable with `cache-tm: false`. ## Caching The binary is cached keyed on version + OS + arch; plugins are cached keyed on the plugin set + OS + arch. Both skip their download step on a cache hit. +### Project parse cache + +kapi keeps the state it derives out of git, under `.kapi/work/` (the project's `.kapi/.gitignore` ignores `work/` and `filters.local.json`). With `cache-tm` on and a recipe in `project-dir`, the action restores one directory of it, `.kapi/work/cache/docs`, before your steps run, and `actions/cache` saves it again when the job ends. kapi records there how it parsed each source and target file, so a later run can replay a file instead of parsing it again. + +A restored parse cache does not change any result. kapi keys each entry by the file's path and content hash, the parse configuration, the recipe and the kapi build, and parses the file again when any of them differs. The test workflow checks this on every change: with the cache restored, `kapi status`, `kapi check`, `kapi check --ship` and `kapi up` must match a cold run exactly, in exit codes, output and every file written, and they must still match after the source changes. + +Everything else under `.kapi/` stays out of the cache: + +- The content memory, terms, voice profile and unit-state record are committed under `.kapi/`, so the checkout already holds them, and kapi builds its local store from them. +- `.kapi/work/store.db` also holds stored targets and staged review decisions. Restored from an earlier run, it reports targets the checkout does not hold, and `kapi status`, `kapi check --ship` and `kapi up` report differently than they would on a cold run. +- `.kapi/work/cache/extractions/` holds `kapi extract` batches for `kapi merge`, and `.kapi/work/cache/redaction/` and `.kapi/work/vault/` hold withheld original values. +- `.kapi/work/cache/sync-cache.json` and `.kapi/work/cache/refs.json` hold server sync state, including a claim token. + +The cache key holds the runner OS, the resolved kapi version and the ref, so a new kapi version starts from an empty cache. How much time the cache saves depends on the formats: container formats such as DOCX replay faster than they parse, while JSON and Markdown parse about as fast as they replay. + ## Platform support | Runner OS | Architectures | diff --git a/action.yml b/action.yml index d6c6032..a7bb2a1 100644 --- a/action.yml +++ b/action.yml @@ -32,16 +32,20 @@ inputs: default: "" cache-tm: description: >- - Restore and persist the project translation memory across CI runs via the - job cache. The TM is derived state kept out of git (AD-009): the latest - branch TM is restored at setup, and the grown TM is saved back at job end - under a run-unique key, so leverage compounds without committing anything. - A cold cache is fine — kapi rebuilds the TM from the committed translations. - Runs only when a kapi.yaml recipe (or legacy *.kapi) is present. Set 'false' to disable. + Carry kapi's parse cache (.kapi/work/cache/docs) between CI runs with the + job cache. kapi keeps the state it derives out of git, under .kapi/work/. + The content memory, terms and unit-state record are committed under + .kapi/, and kapi builds its local store from them in a fresh checkout, so + they need no cache. Only the parse cache is restored: each entry is keyed + by the file's content, the parse configuration, the recipe and the kapi + build, and kapi parses a file again when any of them differs. The store + and the rest of .kapi/work/ are never restored, because restoring them + changes what kapi status, kapi check and kapi up report. Runs only when a + kapi.yaml recipe (or legacy *.kapi) is present. Set 'false' to disable. required: false default: "true" project-dir: - description: "Directory holding the kapi.yaml project (recipe + state) for the TM cache. Default: repository root." + description: "Directory holding the kapi.yaml project whose parse cache is carried between runs. Default: repository root." required: false default: "." @@ -186,9 +190,9 @@ runs: kapi plugins install ${args[@]+"${args[@]}"} "${plugin}" done <<< "${PLUGINS}" - # 11. Detect a kapi project — a committed kapi.yaml recipe (or legacy *.kapi). The TM cache only - # runs where there is a project TM to persist (the .kapi/ state dir is - # gitignored, so it may not exist yet; the recipe is the committed signal). + # 11. Detect a kapi project: a committed kapi.yaml recipe (or legacy *.kapi). + # The parse cache is carried only for a project. The recipe is the + # signal because .kapi/work/ is gitignored and absent from a checkout. - name: Detect kapi project id: detect-project if: inputs.cache-tm != 'false' @@ -199,25 +203,37 @@ runs: if ls "${PROJECT_DIR}"/kapi.yaml >/dev/null 2>&1 || ls "${PROJECT_DIR}"/*.kapi >/dev/null 2>&1; then echo "found=true" >> "$GITHUB_OUTPUT" else - echo "found=false (no kapi.yaml recipe (or legacy *.kapi) in ${PROJECT_DIR}); skipping TM cache" >&2 + echo "found=false (no kapi.yaml recipe (or legacy *.kapi) in ${PROJECT_DIR}); skipping the parse cache" >&2 echo "found=false" >> "$GITHUB_OUTPUT" fi - # 12. Restore/persist the project TM across runs via the job cache. The TM is - # derived state kept out of git (AD-009): restore the latest branch TM at - # setup, and — because the key is run-unique — actions/cache saves the - # grown TM at job end via its own post step. No git writes, no locking - # (additive + rebuildable, last-write-wins); a cold cache rebuilds from - # committed content. Covers the current .kapi/tm.db and the .kapi/cache/ - # derived stores. - - name: Restore project TM cache + # 12. Restore kapi's parse cache, and save it at job end. The key is unique + # to the job and run attempt, so actions/cache saves through its own + # post step; the restore takes the newest cache for the same kapi + # version, preferring the same ref. + # + # Only .kapi/work/cache/docs is carried. kapi keys each entry by the + # file's absolute path and content hash, the parse configuration, the + # recipe and the kapi build, and parses again on any mismatch, so a + # restored entry is either valid for the checkout or ignored. The rest + # of .kapi/work/ stays out of the cache: + # - store.db holds stored targets and the unit working set; restored, + # it changes what kapi status, kapi check --ship and kapi up report. + # - cache/extractions holds kapi extract batches that kapi merge reads. + # - cache/redaction and vault/ hold withheld original values. + # - cache/sync-cache.json and cache/refs.json hold server sync state, + # including a claim token. + # The content memory, terms and unit-state record are committed under + # .kapi/ and come with the checkout. + # + # The kapi version is part of the key because the parse cache keeps the + # entries every build wrote; a new version starts from an empty cache. + - name: Restore kapi parse cache if: inputs.cache-tm != 'false' && steps.detect-project.outputs.found == 'true' uses: actions/cache@v5 with: - path: | - ${{ inputs.project-dir }}/.kapi/tm.db - ${{ inputs.project-dir }}/.kapi/cache - key: kapi-tm-${{ runner.os }}-${{ github.ref_name }}-${{ github.run_id }} + path: ${{ inputs.project-dir }}/.kapi/work/cache/docs + key: kapi-parse-${{ runner.os }}-${{ steps.resolve.outputs.version }}-${{ github.ref_name }}-${{ github.job }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | - kapi-tm-${{ runner.os }}-${{ github.ref_name }}- - kapi-tm-${{ runner.os }}- + kapi-parse-${{ runner.os }}-${{ steps.resolve.outputs.version }}-${{ github.ref_name }}- + kapi-parse-${{ runner.os }}-${{ steps.resolve.outputs.version }}- From 6400119888f6e705bc225a51179e9f4683a617ee Mon Sep 17 00:00:00 2001 From: Asgeir Frimannsson Date: Tue, 15 Sep 2026 08:33:28 +0200 Subject: [PATCH 3/4] test: cache all of .kapi/work to show the state cache test failing Temporary. Restoring the store has to fail the round trip; this commit is reverted once the run shows it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VRid8i4qnuNfE7Lio73Uu4 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index a7bb2a1..2c63992 100644 --- a/action.yml +++ b/action.yml @@ -232,7 +232,7 @@ runs: if: inputs.cache-tm != 'false' && steps.detect-project.outputs.found == 'true' uses: actions/cache@v5 with: - path: ${{ inputs.project-dir }}/.kapi/work/cache/docs + path: ${{ inputs.project-dir }}/.kapi/work key: kapi-parse-${{ runner.os }}-${{ steps.resolve.outputs.version }}-${{ github.ref_name }}-${{ github.job }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | kapi-parse-${{ runner.os }}-${{ steps.resolve.outputs.version }}-${{ github.ref_name }}- From 6e43d7bf322865e6c0f3cd07042a7dceddf78ae0 Mon Sep 17 00:00:00 2001 From: Asgeir Frimannsson Date: Tue, 15 Sep 2026 08:33:58 +0200 Subject: [PATCH 4/4] Revert "test: cache all of .kapi/work to show the state cache test failing" The run on that commit showed the round trip failing with the store restored; the action caches .kapi/work/cache/docs again. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VRid8i4qnuNfE7Lio73Uu4 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 2c63992..a7bb2a1 100644 --- a/action.yml +++ b/action.yml @@ -232,7 +232,7 @@ runs: if: inputs.cache-tm != 'false' && steps.detect-project.outputs.found == 'true' uses: actions/cache@v5 with: - path: ${{ inputs.project-dir }}/.kapi/work + path: ${{ inputs.project-dir }}/.kapi/work/cache/docs key: kapi-parse-${{ runner.os }}-${{ steps.resolve.outputs.version }}-${{ github.ref_name }}-${{ github.job }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | kapi-parse-${{ runner.os }}-${{ steps.resolve.outputs.version }}-${{ github.ref_name }}-