Skip to content

Commit 50840b6

Browse files
authored
ci(release): dependency-ordered, checksum-verified kin-db publish + registry-only consumer smoke (FIR-1021) (#29)
* ci(release): dependency-ordered, checksum-verified kin-db publish + registry-only consumer smoke (FIR-1021) Harden the first-cut auto-publish into a dependency-aware release flow. scripts/publish-kinlab-crates.sh: - Dependency-ordered preflight: reads kin-db's DECLARED registry deps from cargo metadata (not the locally patched/resolved versions) and fails loudly if a required version is missing from the kin index — in particular it enforces the pinned kin-model version (^0.2.0 -> requires 0.2.0 published). - Checksum-identical idempotency: an existing version (HTTP 409) is accepted only when the index cksum is byte-identical to the freshly packaged crate; a version that exists with DIFFERENT bytes fails loudly. - Post-publish proof: re-reads the index cksum, downloads the published .crate, and asserts download == index == locally-packaged checksum. - Reads the registry download template from config.json; DRY_RUN / SKIP_REGISTRY_CHECKS escape hatches for local packaging. ci/registry-smoke/: standalone out-of-tree consumer crate depending on kin-db ONLY via registry = "kin" (default-features=false, features=["vector"] — the set that broke in the version-skew incident). Building it proves the published crate is self-consistent and downloadable. Workflows: registry_publish / publish_registry pinned to a TROY-GATED protected environment (registry-publish); added a post-publish registry_consumer_smoke job that stages the consumer out-of-tree with a registry-only .cargo config (no patches) + fresh CARGO_HOME and builds it. Fixed release.yml's stale 'kin-model and kin-db' label (the script publishes kin-db only). Verified: bash -n + DRY_RUN package/checksum locally; dependency-parsing python emits correct rows incl. the kin-model 0.2.0 pin; consumer main.rs compiles against kin-db. Actual publish + registry resolution are Troy-gated (token). Signed-off-by: Troy Fortin <troy@firelock.io> * fix(release): drop stale unused kin-model 0.1.0 git-patch; resolve kin-model from registry 0.2.0 (FIR-1021) Signed-off-by: Troy Fortin <troy@firelock.io>
1 parent c236c23 commit 50840b6

6 files changed

Lines changed: 486 additions & 14 deletions

File tree

.cargo/config.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ index = "sparse+https://kinlab.ai/registry/cargo/"
1010
[patch.kin]
1111
kin-blobs = { git = "https://github.com/firelock-ai/kin-blobs.git", rev = "5e8b3c3a596163fe6c4b276bcd27c02ffacd694f" }
1212
kin-infer = { git = "https://github.com/firelock-ai/kin-infer.git", rev = "ff1a60c46aa51487b8a492c499b0841592102447" }
13-
kin-model = { git = "https://github.com/firelock-ai/kin-model.git", rev = "aad97894cdd3074894f3d16eec3cf7d43fc4444f" }
1413
kin-search = { git = "https://github.com/firelock-ai/kin-search.git", rev = "cb0680b82f38c8477a0facd33b4cdefde3fa6dfc" }
1514
kin-vector = { git = "https://github.com/firelock-ai/kin-vector.git", rev = "dd743d741c6832e15179ac60720b5218558e10fb" }

.github/workflows/registry-publish.yml

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# Copyright 2026 Firelock, LLC
33

4-
# Registry safety net + idempotent auto-publish.
4+
# Registry safety net + dependency-aware, idempotent auto-publish (FIR-1021).
55
#
66
# registry_smoke: builds the crate against the PUBLISHED Kin registry only —
77
# every local `[patch.kin]` redirect in .cargo/config.toml is stripped first.
88
# Local CI normally builds WITH those sibling-checkout patches, which hides a
99
# crate that cannot resolve from the registry (the exact version-skew failure
1010
# this workflow exists to prevent). Runs on PRs and on main.
1111
#
12-
# registry_publish: on a push to main, packages the crate and POSTs it to the
13-
# registry. The version is read from Cargo.toml (cargo metadata), so a
14-
# version-bump merge auto-publishes without a git tag. A 409 (already
15-
# published) is treated as success, so re-running on an unchanged version is a
16-
# no-op. It only runs after registry_smoke passes.
12+
# registry_publish: on a push to main, runs a dependency-ordered preflight
13+
# (kin-db's required registry deps — notably the pinned kin-model version —
14+
# must already be published), packages the crate, and POSTs it to the registry.
15+
# The version is read from Cargo.toml (cargo metadata), so a version-bump merge
16+
# auto-publishes without a git tag. An existing version (HTTP 409) is accepted
17+
# only when its index checksum is byte-identical to the freshly packaged crate;
18+
# a mismatch fails loudly. After publish it asserts download == index ==
19+
# packaged checksum. It only runs after registry_smoke passes.
20+
#
21+
# TROY-GATED: registry_publish is pinned to the protected `registry-publish`
22+
# GitHub Environment (configure required reviewer = Troy + scope the
23+
# KINLAB_CARGO_TOKEN secret to it). The kin daemon also fails closed without the
24+
# token, so a publish cannot proceed unattended.
25+
#
26+
# registry_consumer_smoke: after publish, builds an out-of-tree throwaway crate
27+
# that depends on kin-db ONLY via `registry = "kin"` (no patches) with a fresh
28+
# CARGO_HOME — proving the published crate is self-consistent and downloadable.
1729

1830
name: Registry Publish
1931

@@ -62,14 +74,71 @@ jobs:
6274
needs: registry_smoke
6375
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
6476
runs-on: ubuntu-latest
77+
# TROY-GATED: protected environment holds the publish token + required
78+
# reviewer. Without it the kin daemon fails closed on publish anyway.
79+
environment: registry-publish
6580
steps:
6681
- uses: actions/checkout@v6
6782

6883
- name: Install Rust toolchain
6984
uses: dtolnay/rust-toolchain@1.96.0
7085

71-
- name: Publish kin-db
86+
- name: Publish kin-db (dependency-ordered + checksum-verified)
7287
run: bash ./scripts/publish-kinlab-crates.sh
7388
env:
7489
KINLAB_CARGO_REGISTRY_URL: ${{ vars.KINLAB_CARGO_REGISTRY_URL }}
7590
KINLAB_CARGO_TOKEN: ${{ secrets.KINLAB_CARGO_TOKEN }}
91+
92+
registry_consumer_smoke:
93+
name: Registry-only consumer smoke (post-publish)
94+
needs: registry_publish
95+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
96+
runs-on: ubuntu-latest
97+
steps:
98+
- uses: actions/checkout@v6
99+
100+
- name: Install Rust toolchain
101+
uses: dtolnay/rust-toolchain@1.96.0
102+
103+
- name: Resolve published kin-db version
104+
id: ver
105+
run: |
106+
V="$(cargo metadata --no-deps --format-version 1 \
107+
| python3 -c 'import json,sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"]=="kin-db"))')"
108+
echo "version=$V" >> "$GITHUB_OUTPUT"
109+
110+
- name: Stage consumer crate out-of-tree (no repo .cargo patches inherited)
111+
env:
112+
VERSION: ${{ steps.ver.outputs.version }}
113+
run: |
114+
set -euo pipefail
115+
dest="$RUNNER_TEMP/consumer"
116+
rm -rf "$dest"
117+
cp -r ci/registry-smoke "$dest"
118+
mkdir -p "$dest/.cargo"
119+
# Registry alias ONLY — no [patch.*] redirects, so kin-db and its deps
120+
# must resolve from the published registry.
121+
cat > "$dest/.cargo/config.toml" <<'EOF'
122+
[registries.kin]
123+
index = "sparse+https://kinlab.ai/registry/cargo/"
124+
EOF
125+
# Pin the consumer to the exact just-published kin-db version.
126+
python3 - "$dest/Cargo.toml" "$VERSION" <<'PY'
127+
import re, sys
128+
path, version = sys.argv[1], sys.argv[2]
129+
with open(path, "r", encoding="utf-8") as fh:
130+
text = fh.read()
131+
text = re.sub(r'(kin-db\s*=\s*\{\s*version\s*=\s*")[^"]*(")',
132+
lambda m: m.group(1) + version + m.group(2), text, count=1)
133+
with open(path, "w", encoding="utf-8") as fh:
134+
fh.write(text)
135+
print("pinned consumer to kin-db", version)
136+
PY
137+
138+
- name: Build consumer from the kin registry only (fresh cache)
139+
working-directory: ${{ runner.temp }}/consumer
140+
env:
141+
# Fresh CARGO_HOME forces a clean download from the registry rather
142+
# than reusing any cached sibling artifacts.
143+
CARGO_HOME: ${{ runner.temp }}/fresh-cargo-home
144+
run: cargo build --locked || cargo build

.github/workflows/release.yml

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,66 @@ jobs:
5050
prerelease: ${{ contains(github.ref_name, '-') }}
5151

5252
publish_registry:
53-
name: Publish crates to KinLab
53+
name: Publish kin-db to KinLab registry
5454
needs: release
5555
runs-on: ubuntu-latest
56+
# TROY-GATED: protected environment holds the publish token + required
57+
# reviewer. The kin daemon also fails closed without the token.
58+
environment: registry-publish
5659
steps:
5760
- uses: actions/checkout@v6
5861
- uses: dtolnay/rust-toolchain@1.96.0
59-
- name: Publish kin-model and kin-db
62+
# Dependency-ordered preflight + checksum-verified, idempotent publish.
63+
# TAG_NAME is an optional consistency check against the Cargo version.
64+
- name: Publish kin-db (dependency-ordered + checksum-verified)
6065
run: bash ./scripts/publish-kinlab-crates.sh
6166
env:
6267
TAG_NAME: ${{ github.ref_name }}
6368
KINLAB_CARGO_REGISTRY_URL: ${{ vars.KINLAB_CARGO_REGISTRY_URL }}
6469
KINLAB_CARGO_TOKEN: ${{ secrets.KINLAB_CARGO_TOKEN }}
70+
71+
registry_consumer_smoke:
72+
name: Registry-only consumer smoke (post-publish)
73+
needs: publish_registry
74+
runs-on: ubuntu-latest
75+
steps:
76+
- uses: actions/checkout@v6
77+
- uses: dtolnay/rust-toolchain@1.96.0
78+
79+
- name: Resolve published kin-db version
80+
id: ver
81+
run: |
82+
V="$(cargo metadata --no-deps --format-version 1 \
83+
| python3 -c 'import json,sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"]=="kin-db"))')"
84+
echo "version=$V" >> "$GITHUB_OUTPUT"
85+
86+
- name: Stage consumer crate out-of-tree (no repo .cargo patches inherited)
87+
env:
88+
VERSION: ${{ steps.ver.outputs.version }}
89+
run: |
90+
set -euo pipefail
91+
dest="$RUNNER_TEMP/consumer"
92+
rm -rf "$dest"
93+
cp -r ci/registry-smoke "$dest"
94+
mkdir -p "$dest/.cargo"
95+
cat > "$dest/.cargo/config.toml" <<'EOF'
96+
[registries.kin]
97+
index = "sparse+https://kinlab.ai/registry/cargo/"
98+
EOF
99+
python3 - "$dest/Cargo.toml" "$VERSION" <<'PY'
100+
import re, sys
101+
path, version = sys.argv[1], sys.argv[2]
102+
with open(path, "r", encoding="utf-8") as fh:
103+
text = fh.read()
104+
text = re.sub(r'(kin-db\s*=\s*\{\s*version\s*=\s*")[^"]*(")',
105+
lambda m: m.group(1) + version + m.group(2), text, count=1)
106+
with open(path, "w", encoding="utf-8") as fh:
107+
fh.write(text)
108+
print("pinned consumer to kin-db", version)
109+
PY
110+
111+
- name: Build consumer from the kin registry only (fresh cache)
112+
working-directory: ${{ runner.temp }}/consumer
113+
env:
114+
CARGO_HOME: ${{ runner.temp }}/fresh-cargo-home
115+
run: cargo build --locked || cargo build

ci/registry-smoke/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright 2026 Firelock, LLC
3+
#
4+
# Registry-only consumer smoke (FIR-1021).
5+
#
6+
# A throwaway crate that depends on kin-db ONLY through the published "kin"
7+
# registry — no path/git patches. Building it after a publish proves the
8+
# published kin-db crate is self-consistent and downloadable by a third party:
9+
# its declared registry dependencies (kin-model, kin-search, kin-vector at the
10+
# pinned versions) must all resolve and compile from the registry alone.
11+
#
12+
# `[workspace]` makes this its OWN workspace so it is never pulled into the
13+
# kin-db workspace. The kin-db version below is a placeholder; the registry
14+
# consumer-smoke CI job rewrites it to the exact just-published version before
15+
# building.
16+
17+
[package]
18+
name = "kin-db-registry-smoke"
19+
version = "0.0.0"
20+
edition = "2021"
21+
license = "Apache-2.0"
22+
publish = false
23+
24+
[workspace]
25+
26+
[dependencies]
27+
# Resolved ONLY from the kin registry. default-features = false + features =
28+
# ["vector"] mirrors the consumer feature set that broke in the version-skew
29+
# incident (kin-db pulled into kin/kin-bench without the embeddings stack), so
30+
# this smoke covers the historically fragile resolve, not just the fat default.
31+
kin-db = { version = "0.0.0", registry = "kin", default-features = false, features = ["vector"] }

ci/registry-smoke/src/main.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2026 Firelock, LLC
3+
//
4+
// Registry-only consumer smoke (FIR-1021). Exercises a few kin-db public APIs
5+
// so the published crate is actually compiled and LINKED — not merely resolved
6+
// — when built against the registry. If the published kin-db references a
7+
// dependency version that is not in the registry, this binary fails to build.
8+
9+
fn main() {
10+
// Construct + round-trip an empty snapshot through the public API, then
11+
// build the in-memory graph. These symbols span the storage + engine
12+
// surfaces, forcing a broad slice of the published crate to link.
13+
let snapshot = kin_db::GraphSnapshot::empty();
14+
let graph = kin_db::InMemoryGraph::from_snapshot(snapshot);
15+
std::hint::black_box(&graph);
16+
17+
println!("kin-db registry-smoke OK — published crate is self-consistent");
18+
}

0 commit comments

Comments
 (0)