feat: make Keryx deployable with pluggable storage, OCI sharing, Postgres and a config file - #12
Merged
Merged
Conversation
… age gate rustls 0.23.45 fixes the advisory but is five days old, so the 14-day publish-age gate refuses it. Same temporary pattern as the chacha20 entry.
tiny-keccak is CC0-1.0 and arrives with reqsign's profile file parser, which the S3 backend needs. CC0 is a public domain dedication, and kache allows it the same way.
The root keeps the keryx binary, so cargo build --release still produces target/release/keryx. Dependencies move to [workspace.dependencies] so the internal crates that follow declare each one exactly once.
types.rs and ids.rs move unchanged. sha256_hex leaves main.rs and now / format_timestamp leave db.rs, because the binary cannot export to its own libraries and the CLI should not reach into the database for a string format.
policy.rs, storage.rs and db.rs each become the lib.rs of keryx-policy, keryx-store and keryx-db. The test helpers test_store() and test_connection() sit behind a test-support feature so dependent crates can still use them across the crate boundary.
render.rs and pdf.rs become keryx-render; server.rs, notifications.rs and realtime.rs become keryx-server. Each asset moves next to the crate that embeds it, so the nine include paths are unchanged. The two dashboard fragment renderers go from pub(crate) to pub because the server now calls them across a crate boundary.
…yx-client client.rs becomes the crate root and gitmeta.rs its one module.
main.rs, cli.rs and tui.rs are what remain in src/. Their crate:: paths become crate names, the temporary aliases in main.rs go, and the root dependency list shrinks to what the binary itself uses.
Exemptions generated by cargo vet for reqwest 0.13.4 and the platform certificate verifier it uses with rustls. An exemption records that a crate is unreviewed; auditing happens before release.
OpenDAL's transport at 0.58.2 needs reqwest 0.13, so Keryx moves first and the build keeps one HTTP stack. rustls-no-provider keeps aws-lc-rs out, so rustls becomes a direct dependency and main() installs the ring provider once, before the CLI or the server can make an HTTPS request. The provenance test installs it too because it makes its own requests. reqwest gains the query feature: 0.13 gates RequestBuilder::query behind it and keryx-client fails to compile without it.
Exemptions generated by cargo vet for opendal 0.58.2, reqsign 3.3 and their transitives. An exemption records that a crate is unreviewed; auditing happens before release.
…eature opendal-core, the fs and s3 services, the reqwest transport and reqsign are pinned exactly as kache pins them, defaults off. The transport takes rustls-no-provider so ring stays the only TLS provider. tokio gains fs for the OpenDAL filesystem service, and async-trait arrives for the BlobBackend trait that follows. s3 is a default feature forwarded root -> keryx-server -> keryx-store, so --no-default-features gives a disk-only build. A test builds an S3 and an fs operator to prove the stack compiles and links before logic lands.
BlobBackend is the seam the rest of Keryx will hold as Arc<dyn>. One OpenDalBackend covers disk and S3, picked by BackendConfig through create_backend, and a memory operator sits behind test-support. The disk operator roots at the data directory so existing files read in place, always sets atomic_write_dir to <data-dir>/.staging (OpenDAL then syncs before renaming, which today's code never does), empties that directory at startup, and checks both sit on one filesystem. remove_many tidies the emptied drafts/<id>/ directory on disk only. The S3 operator follows kache: no retry layer, Content-MD5, the prefix as operator root, and a credential provider that keeps --s3-profile and credential_process working without touching the process environment. The helper runs through std::process on a blocking task, so tokio needs no process feature. BlobStore stays until its callers move; its object_key now delegates so the key shape has one definition.
The fixture is three uploads across two drafts made with the released 0.5.1 binary, plus the object keys, hashes and sizes it recorded. The disk backend must read every key in place with matching content before the old storage code can go.
record_upload opened a write transaction and called store.put() inside it. With a network store that would hold the database lock for a multi-second PutObject, and it cannot await at all. resolve_upload_target now answers the draft id with a cheap read, the caller mints the version id and key and writes the blob, and record_upload records metadata only. Because the draft can now vanish between the two steps, record_upload re-checks that it is live inside its transaction and returns DraftNotFound; the upload handler then removes the blob it wrote, best effort. The blob still lands before the metadata commits, so the ordering invariant holds. keryx-db no longer depends on keryx-store. The handler keeps the old synchronous BlobStore for one more commit.
AppState holds an Arc<dyn BlobBackend> built from the new --storage and --s3-* flags. Startup probes the store with a write and a delete and aborts with a readable error, and the banner reports the store and probe time. The upload handler now awaits the blob put with no database lock held and removes its blob when the record step rejects. serve_draft, publish_pdf and remove_blobs are async; a version whose blob is missing serves a clean not-found instead of a 500, and purge issues one remove_many call rather than one request per key. The synchronous BlobStore and test_store() are deleted now that the 0.5.1 compatibility gate has passed; server tests run on the memory backend.
Both are written against dyn BlobBackend in keryx-store and run offline with the server stopped. migrate copies every blob a version row records, reads each back from the destination and checks it against the stored sha256. A key already there with the recorded size is skipped, so an interrupted run resumes by re-running. --remove-source deletes only after every key verified, and any failure exits non-zero with the source untouched. It works in both directions. gc diffs the store against the recorded keys. It is report-only unless --delete, and skips anything younger than one hour, because an upload now writes its blob before its row commits. The S3 flags move into a shared S3Args so serve and the storage commands read the same flags and environment variables.
Covers the --storage and --s3-* flags, the minimal S3 permissions, the startup probe, moving between stores, orphan collection, and the caveat that S3 alone does not make Keryx multi-node while SQLite stays local.
Exemptions generated by cargo vet. An exemption records that a crate is unreviewed; auditing happens before release.
oci-client 0.17 and docker_credential 1.4 arrive with defaults off behind a default share feature. oci-client takes neither TLS feature: rustls-tls hard-wires aws-lc-rs twice over and native-tls means OpenSSL. It compiles because keryx-share declares reqwest with rustls-no-provider itself, and a test builds a client to prove it links on the ring provider. cargo tree -i aws-lc-rs finds nothing, the lockfile holds one reqwest, and CI now fails if aws-lc-rs or aws-lc-sys ever enters the graph.
A draft version becomes a single-layer artifact: a text/html layer holding the exact stored bytes and named <title-slug>-v<n>.html through the ORAS title annotation, a self-describing config blob, an artifactType, and standard plus namespaced manifest annotations. Following synapse's share.rs, async stays inside this crate behind a current-thread runtime, and it is the only crate that depends on oci-client and docker_credential. Explicit versions only, unlike synapse: push writes :v<n> and nothing else, and pull and inspect accept a :v<n> tag or a digest, checked after parsing because Reference::parse rewrites a bare reference to :latest. Push first reads the tag: the same manifest is a no-op, a different one is refused without --force. Pull verifies the layer's sha256 against the recorded content hash, and a mismatch or a missing hash is a hard error. Registry auth resolves KERYX_REGISTRY_TOKEN, then KERYX_REGISTRY_USER and KERYX_REGISTRY_PASS, then docker credentials, then anonymous.
Api::version answers the draft summary plus the requested version, or the latest, which is what keryx share needs to describe the HTML it pushes.
share fetches a version's exact bytes through /raw and pushes it from the operator's machine with their own registry credentials, so the server keeps no registry secrets. The artifact title comes from that version's own <title>, and its content hash is the sha256 of the bytes pushed. pull verifies integrity, then runs validate_html against the destination server's policy before uploading anything: a pulled artifact is untrusted HTML and there is no way to skip the gate. It replays the original git provenance through the upload metadata and records the reference, tag plus resolved digest, as the upload filename, so there is no schema change. --output writes a file and touches no server. inspect reads the manifest and config only. An ignored integration test covers the real interop path: share, then a plain oras pull in a subprocess asserting filename and bytes, then inspect and pull. It passes against registry:3.1.1.
The README gains a Sharing section that leads with the plain oras pull recipe. keryx-read learns to read a shared reference through keryx pull --output, and html-communication learns keryx share, on request only.
stylo 0.8.0 imports every derive_more macro with #[macro_use]. SeaORM enables derive_more's debug feature, Cargo unifies it across the build, and every #[derive(Debug)] in stylo then resolves to derive_more's Debug, whose expansion overflows on stylo's recursive types. fulgur 0.40.0 is the newest fulgur and pins this stylo, so there is no release to move to. The fork is the published 0.8.0 commit plus that fix, pinned by revision. cargo deny allows that one git source. cargo vet treats the fork as third-party (audit-as-crates-io) and carries the same exemption at the git revision; the policy and exemption change with the patch because vet cannot pass without all three. Drop the patch once fulgur moves to a fixed stylo.
Exemptions generated by cargo vet for sea-orm 2.0.2, sea-orm-migration, sqlx 0.9, sea-query and their transitives. An exemption records that a crate is unreviewed; auditing happens before release.
Both take default-features = false with sqlx-sqlite, sqlx-postgres and runtime-tokio-rustls, plus macros on sea-orm. Nothing uses them yet: rusqlite stays the live path until the parity test passes. sqlx accepts libsqlite3-sys from 0.30.1, so it shares rusqlite's bundled SQLite and the build links one copy. No new crypto backend arrives; aws-lc-rs is still absent.
One baseline migration, in Rust, branching on the backend. On SQLite it runs the init() create block through execute_unprepared; the block moves byte for byte into a constant that rusqlite's init() now shares, so there is one copy. On Postgres it creates the same tables with BOOLEAN for the two flag columns. It forces a transaction on both backends, because SeaORM only wraps migrations by default on Postgres. The Postgres block also uses BIGINT for version_number, file_size and attempts. The plan says only booleans diverge, but a Postgres INTEGER is 32-bit and the entities read these as i64, which sqlx refuses to decode from INT4. connect_sqlite sets what SeaORM does not: WAL, foreign keys, a 5 second busy timeout (rusqlite's default, so unchanged), create if missing, and a pool of one. A test asserts WAL and foreign keys are on.
One entity per table, matching the existing columns exactly; a test compares each entity's columns with PRAGMA table_info. Timestamps stay String because they are TEXT on both backends, and git_dirty and has_inline_script map to bool. The plan counts six entities, but the schema has five tables.
An existing keryx.db has no seaql_migrations table; it sits at user_version 0, 1 or 2 with hand-rolled upgrade history. open_sqlite tells three cases apart: a fresh database goes to the migrator, a managed one runs what is pending, and a legacy one is adopted. Adoption takes a VACUUM INTO snapshot first (a plain copy can miss pages still in the WAL), then in one immediate transaction runs the IF NOT EXISTS create block for tables that arrived later, the two old conditional upgrade steps ported from init(), and inserts the baseline row. The plan lists only the two steps, but a pre-0.5.0 database also lacks the push tables, which init() used to add the same way. user_version is read and never written, so an older binary still understands the file. The Adoption value says exactly what changed, for the server log. Fixtures rebuild history without Keryx code: the first release's schema and the old steps as SQL, plus a database written by the released 0.5.1.
Run at user_version 0, 1 and 2 (columns appended by ALTER) and against a database written by the released 0.5.1 (every column inline), which are the two legacy populations. Level 1, schema: an adopted database matches one the migrator builds from empty, by column name, declared type, nullability, default and primary key, plus indexes and their columns. Not raw sqlite_master text or column order, which already differ between the populations. Level 2, data: adoption leaves every row exactly as the old rusqlite upgrade path does, and the old query layer answers identically from both. Those answers are pinned as golden JSON so they outlive rusqlite and the ported store can be held to them. Level 3, end to end: the real binary serves, uploads to, lists and publishes from an adopted 0.5.1 installation exactly as it does from an untouched one, and serves what 0.5.1 stored byte for byte. The plan reads level 2 through DraftStore, which does not exist until the store port, so here it is judged through the old query layer instead.
NewUpload, UploadOutcome, the error enums, ServedVersion, BlobRecord, PendingDelivery and normalize_wake_time mean the same thing on every backend, so they move to their own module where the SeaORM store can share them. They stay re-exported from the crate root; no caller changes.
DraftStore is the metadata seam the server will hold as Arc<dyn>. SeaOrmStore ports every function of the rusqlite layer with the same names, semantics and error variants, written once through SeaORM's builder for SQLite and Postgres. Opening a SQLite store goes through adoption, so the pragmas and the pool of one apply, and every write transaction begins immediate. Three shapes changed without changing meaning. INSERT OR IGNORE became an ON CONFLICT DO NOTHING insert. The INSERT ... SELECT that queued deliveries became a select of opted-in subscriptions and an insert_many in the same transaction. record_due_wakes built its NOT EXISTS key by concatenating in SQL, which has no portable builder form, so it now computes the wake keys and skips the ones already recorded. The old test suite is ported onto the store unchanged in meaning, beside a test that 24 concurrent uploads over two handles surface no SQLITE_BUSY and number their versions without gaps. The parity gate now also asks the store the golden questions, and it answers exactly as the old query layer did at every legacy level.
AppState and the notification dispatcher take the store. Arc<Mutex<Connection>> and every .lock().unwrap() are gone, so no handler holds a std mutex on a Tokio worker any more, and the server crate no longer depends on rusqlite. Startup opens the SQLite store through adoption: a database from an older Keryx is snapshotted with VACUUM INTO, brought under migration management in place, and the banner says what happened. --no-backup skips the snapshot. /healthz asks the store to ping. Server tests run on an in-memory store. The few that assert on rows no store method exposes keep the concrete store and use a test-only peek.
…mmands storage migrate and storage gc opened SQLite directly. They now ask the store, so they work on whichever database the server uses once Postgres arrives, and they adopt a legacy database the same way the server does.
The parity gate compares an adopted database row for row with one upgraded by rusqlite's init(). Those rows are now golden files as well, generated by the old code while it still exists, so the comparison survives its removal.
The hand-written rusqlite layer, its init() upgrade steps and its tests go. Every caller already uses DraftStore, the old tests were ported onto the store earlier, and the upgrade steps live on in adoption. The parity gate no longer has old code to compare against, so it judges adoption and the store against the golden files the old code generated: the rows its upgrade path produced and the answers its query layer gave, at user_version 0, 1 and 2 and for a database written by 0.5.1.
A postgres:// URL in --database-url or KERYX_DATABASE_URL selects Postgres. Absent means SQLite at --db, so every existing deployment stays on its current path with no new flags. --db-pool-size defaults to 1 on SQLite and 4 on Postgres. The flags live in a shared DatabaseArgs, so the offline storage commands reach the same database as the server. The Postgres pool pings a connection before handing it out and has connect, acquire and idle timeouts, so a failover heals without a restart: killing the database under a running server gives 503s from /healthz, then 200 again once it is back, in the same process. TLS is configured in the URL through sslmode and sslrootcert. The URL can carry a password, so the banner and every error print it with credentials and query string removed. The store's test factory opens in-memory SQLite, or a fresh schema in the Postgres named by KERYX_TEST_DATABASE_URL, so one suite runs on both. The keryx-db and keryx-server suites pass on postgres:18.6.
Two things that are safe on SQLite by construction need a lock on Postgres, and without them the new tests fail there. SeaORM's migrator takes no lock, and a rolling update starts a new pod while the old one runs. Two migrators creating the schema together fail on a duplicate pg_type key. The migrator now runs inside a transaction holding pg_advisory_xact_lock, so a second pod waits and then finds nothing pending. MAX(version_number) + 1 can collide between concurrent uploads to one draft. UNIQUE (draft_id, version_number) turns that into an error rather than corruption, but a user would still see it. record_upload now reads the draft row FOR UPDATE on Postgres. SQLite has no row locks and needs none: its immediate transaction already serialises writers. Tests: sixteen concurrent uploads to one draft number their versions without gaps on both backends, and four migrators started together all succeed with exactly one creating the schema.
A Postgres 18.6 service container and KERYX_TEST_DATABASE_URL turn the keryx-db and keryx-server suites into a Postgres run; the existing test job stays the SQLite run. Custom selects are only checked at runtime, so the whole store suite has to pass on both backends.
The flags, TLS with a private CA, required privileges, connecting past a pooler, probes, manual disaster recovery, and the single-replica boundary.
Exemptions generated by cargo vet. An exemption records that a crate is unreviewed; auditing happens before release.
config takes default-features = false with only toml: Keryx reads one TOML file, so the json, yaml, ini, ron and json5 parsers, the async support and case conversion stay out. toml_edit was already in the lockfile as a transitive; it becomes direct so Keryx can update the config file without losing the user's comments.
keryx-config owns the config file: where it lives, its typed schema, strict validation, and updating it in place. It is the lowest layer beneath environment variables and flags, which stay with clap. The file is $XDG_CONFIG_HOME/keryx/config.toml, then ~/.config/keryx on every platform, then the platform's own config directory; --config or KERYX_CONFIG names another. Sections follow concerns ([client], [server], [database], [storage] and [storage.s3]) and keys follow the flags they stand in for. A missing file is fine. An unknown key, a wrong type or an unknown storage kind is an error naming the file and the entry, so a typo can never silently do nothing. A leading ~/ in a path is the home directory. Keryx writes one key, client.api_url, through toml_edit, so the rest of a hand-written file survives, comments included. A file it creates is 0600, because the file may come to hold a server API key or database password.
The binary loads the config file first and hands its values to clap as defaults, so clap resolves flag > environment variable > config.toml > built-in default on its own. Every existing environment variable keeps its name, and --help shows the value in effect. A value from the file also satisfies a required flag, which is how client.share_to gives keryx share a default --to. --help never prints server.api_key or database.url. --config and KERYX_CONFIG name another file; the flag is read from the raw arguments because the file has to load before clap parses. An invalid file stops any command with the file and entry named, exit status 2. The integration tests now cut the spawned binary off from the developer's own config file.
The client resolves its API URL as flag > KERYX_API_URL > client.api_url in config.toml > localhost, and keryx auth set --api-url writes that key instead of ~/.keryx/config.json. One config file is enough. The first run that finds the old JSON moves its apiUrl into config.toml and deletes it. A URL already in the TOML wins. If the TOML cannot be written the JSON stays, so nothing is lost. The API key is a secret, not config, and stays in ~/.keryx/credentials.json.
Pluggable blob storage, OCI sharing, the SeaORM store with Postgres, and the config file change how Keryx is run and what it depends on, which is a minor version while Keryx is pre-1.0.
Imports the audit sets published by Mozilla, Google, the Bytecode Alliance, Embark, ISRG, Zcash, Fermyon and Ariel OS. Their audits replace 128 exemptions: 121 crates are now fully audited and 7 partially. imports.lock pins what was imported, so cargo vet --locked stays offline in CI.
… trust Records cargo vet trust for 129 crates, each for one named publisher, never as a blanket --all. The rule: only where at least two of the imported organisations (Mozilla, ISRG, Zcash, Ariel OS, the Bytecode Alliance and others) already trust that publisher. The publishers are dtolnay, epage, kennykerr, BurntSushi, seanmonstar, alexcrichton, cuviper, Amanieu, Darksonn, JohnTitor, rust-lang-owner, mbrubeck, Thomasdezeeuw, sunfishcode and str4d. Publishers only Mozilla or only Zcash trusts are left out. Trust is delegation, not review: it says we accept these publishers' releases of these crates because organisations that do review them do. Exemptions drop from 635 to 508.
Each was read in full and certified safe-to-deploy with notes on what was checked: docker_credential, pathdiff, reqsign-file-read-tokio, sqlx-macros, olpc-cjson, atoi and opendal-service-fs. The notes record the two 'unsafe impl Sync' in OpenDAL's fs service and why they are sound, that docker_credential's helper error carries the helper's output, and that sqlx-macros delegates to sqlx-macros-core, which is not covered. The reviewer field says these were read by an AI assistant for SimCube, because that is what happened. Exemptions drop from 508 to 501.
…nreviewed How a crate gets past cargo vet, in order of preference: imported audits, trust in a publisher at least two imported organisations trust, our own audits, and exemptions. It names the large dependency families that have no public audit and are exempted with open eyes, notes that 65 exempted crates are locked but never compiled, and lists the mitigations that do not depend on review.
Graceful shutdown waits for open connections, and the dashboard's SSE stream never finishes on its own, so with a dashboard tab open Ctrl-C hung until the tab was closed. Under systemd it only ended when TimeoutStopSec killed it, making every restart take the full timeout. Shutdown now ends the live-update streams, so the browser sees a clean end of stream and reconnects when the server is back. SIGTERM triggers the same graceful path as Ctrl-C, where before only Ctrl-C did and systemd's signal was never handled. A second signal exits at once, so a terminal is never stuck behind a connection that refuses to finish. The behaviour predates this branch; the shutdown code was unchanged from 0.5.1. A test holds a stream open over a real listener and requires shutdown to complete, and fails without the fix.
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (7)
📒 Files selected for processing (91)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
CI installed the newest prebuilt cargo-vet, 0.10.0 from 2024. The audits were imported with 0.10.2, which records crates published through crates.io Trusted Publishing as trusted-publisher entries with no user id. 0.10.0 cannot parse those, so cargo vet --locked failed on imports.lock before vetting anything. CI now builds 0.10.2 from crates.io, once, since rust-cache keeps ~/.cargo/bin. The supply-chain README names the version to use locally.
cargo-vet 0.10.2's lockfile pins rustix 0.37, which enables internal rustc attributes when it detects a nightly compiler, and the pinned nightly rejects them. Only the tool build uses stable; Keryx stays on nightly, which the minimum-publish-age gate needs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Keryx could only run with a local disk: draft HTML lived in files, metadata lived in a local SQLite file, and everything was one crate where any module could reach any dependency. This makes Keryx deployable without a persistent volume, while an existing installation upgrades in place with no flags and can go back to 0.5.1 on the same files.
What changed
src/becomes eight internal crates, so boundaries like "the TUI cannot touch the database" are compile errors rather than conventions. A pure move: the 58 test names were identical before and after.BlobBackendtrait with one OpenDAL implementation for disk and S3. The blob write moved out of the database transaction, so a slow object store can never hold the database lock. Adds--storageand--s3-*, a startup probe,keryx storage migrateandkeryx storage gc.keryx share,pullandinspect. A draft version becomes a singletext/htmllayer, so someone with no Keryx gets the file from oneoras pull. Pushes happen on the client with the operator's own registry credentials. Explicit:v<n>tags or digests only, never:latest.DraftStoretrait replaces hand-written rusqlite. An existingkeryx.dbis adopted in place after aVACUUM INTObackup, andPRAGMA user_versionis left alone so rollback stays safe.--database-urlselects Postgres, with an advisory lock around migrations andFOR UPDATEon the draft row.~/.config/keryx/config.toml, resolved as flag > environment variable > config.toml > built-in default. Every existing environment variable keeps its name. The old~/.keryx/config.jsonis migrated into it.Parity with existing installations
This was the acceptance criterion. The parity gate runs at
user_version0, 1 and 2 and against a database written by the released 0.5.1:A data directory written by the released 0.5.1 binary reads in place. Rollback was checked for real: 0.6.0 adopted a 0.5.1 database, 0.5.1 then served and uploaded on the same files, and 0.6.0 picked it back up.
Supply chain
aws-lc-rsnever enters the build; CI now fails if it does.oci-clientbuilds on ring with no TLS feature of its own, and there is one reqwest in the lockfile.stylois built from the SimCubeLtd fork pinned by revision. SeaORM enablesderive_more'sdebugfeature, and stylo 0.8.0 imports everyderive_moremacro with#[macro_use], so its#[derive(Debug)]stopped compiling. The fork carries that one fix; drop it when fulgur moves to a fixed stylo.cargo vetexemptions went from 763 to 501 through imported audits, per-crate trust where at least two imported organisations agree, and seven hand audits.supply-chain/README.mdrecords the policy and the dependency families that are knowingly unreviewed.deny.tomlallows CC0-1.0, and temporarily ignores RUSTSEC-2026-0285 until rustls 0.23.45 clears the 14-day age gate on 28 September.Verified beyond the test suite
Against real services: MinIO (migrate, resume, source removal, serving, gc),
registry:3.1.1with a plainoras pull, a Harbor instance with bearer-token auth, and Postgres 18.6 including killing the database under a running server, which heals without a restart. Thekeryx-dbandkeryx-serversuites pass on both SQLite and Postgres, and CI now runs them on both.Every commit passes
cargo denyandcargo vet --locked. On the head: fmt, clippy, 119 tests passed with 1 ignored (the registry round trip, which needsorasand a registry).Notes for review
Summary by CodeRabbit