Skip to content

LAB-275: cross-SDK feature-gap synthesis — code-verified matrix refresh (folds in LAB-272 + LAB-273) - #29

Closed
27Bslash6 wants to merge 9 commits into
mainfrom
lab-275-feature-gap-synthesis
Closed

LAB-275: cross-SDK feature-gap synthesis — code-verified matrix refresh (folds in LAB-272 + LAB-273)#29
27Bslash6 wants to merge 9 commits into
mainfrom
lab-275-feature-gap-synthesis

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR performs a comprehensive, code-verified refresh of the SDK feature parity matrix (sdk-feature-matrix.md), folding in the findings from two prior audits (LAB-272 protocol adherence and LAB-273 backend parity) into a single cross-SDK feature-gap synthesis (LAB-275).

Key Changes

Version & Metadata Updates

  • Bumped Python SDK to v0.13.0, corrected Rust MSRV to 1.85+, and updated cachekit-core dependency to the 0.3 line.
  • Updated TypeScript SDK notes to reflect Node 22+ (async-only) requirement.

Corrected Feature Cells (audit reconciliation)

  • Key rotation: Corrected from previously showing Python ✅ / Rust ✅ to ❌ across all SDKs — code inspection revealed rotate_key() is a NotImplemented stub, Python's binding has no callers (only mismatch detection is live), and TS only monitors nonce exhaustion. Tracked as LAB-516.
  • Hardware acceleration detection: Corrected to reflect that AES detection lives in cachekit-core but is surfaced only by Python; Rust doesn't re-export it and TS exposes nothing (previous N/A was wrong). Tracked as LAB-523.
  • DynamoDB: Corrected Python from ✅ to ❌ — it was never shipped; the prior claim traced to a custom-backend tutorial (extension point, not support).
  • Serializers: Clarified Orjson and Arrow are Python-ecosystem serializers with specific extras; TS Arrow corrected from "Planned" to ❌ (no code behind it, LAB-524).

New Sections Added

  • Backend Abstraction: Documents the required interface and optional capabilities each SDK's backend contract must satisfy, plus lock-API shape divergence and Redis capability-coverage gaps.
  • Observability: New matrix covering metrics, structured logging, distributed tracing (absent fleet-wide), and SaaS telemetry headers, with TS cells re-verified after cachekit-ts#75.

Expanded Reliability Features

  • Added rows for Retry, Graceful degradation, and Cross-instance invalidation (pub/sub), with detailed footnotes on which capabilities are wired vs. dead code (e.g., Python's pub/sub built but never wired — LAB-520).
  • Documented the completed CWE-532 lock-id header migration across all three SDKs.

Protocol Compliance Refresh

  • Updated interop mode status: Python released (v0.13.0), Rust and TS merged but unreleased.
  • Clarified Rust key-generation "N/A" (macro-internal legacy derivation) and AAD component counts (Python 5, Rust/TS/interop 4).
  • Reframed test-vector coverage as "in CI," noting every vector file is now enforced by at least one CI.

27Bslash6 and others added 8 commits July 21, 2026 01:05
…(LAB-273 audit)

Backend-parity audit findings, verified against code (py 0.12.0, rs 0.3.0,
ts 0.1.2):

- DynamoDB ships in NO SDK. The Python ✅ traced to the custom-backend
  tutorial (docs/backends/custom.md) — an extension-point example, not
  shipped support. Row corrected to ❌ across the board.
- TS Cloudflare Workers cell N/A → ❌: Workers is a plausible target for
  TS but blocked by NAPI-native crypto and ioredis; N/A misstates it.
- New 'Backend Abstraction' section: required interface per SDK
  (health required py/rs, absent ts; close()+keyPrefix ts-only), optional
  capability interfaces, per-backend implementation coverage, and the
  lock-API shape divergence (py context-manager vs rs/ts lock_id token).
- Reliability rows qualified per backend: Python locking is Redis+SaaS
  (not SaaS-only); rs/ts locking SaaS-only; TS TTL inspection SaaS-only.
- Lock-id header migration (CWE-532) recorded complete in all three SDKs:
  py#131 closed, rs#24 closed, ts ships the header (ts#63 open only for
  an unrelated NAPI-rebuild item).
- Backend-selection note: Python is the only SDK with env backend
  auto-detection; rs/ts env vars are SaaS-credential-only. Recorded as a
  finding, not a prescription.
- py version 0.11.1 → 0.12.0 (v0.12.0 tag exists).

Co-authored-by: multica-agent <github@multica.ai>
…reality (LAB-272)

Audit of py/rs/ts main branches against the four protocol requirements:

- Interop mode: merged in cachekit-py (#220) and cachekit-ts (#71), in
  review for cachekit-rs (#33) — the '❌ Not implemented' row was stale.
- Test vectors row now states what each SDK's CI actually executes;
  wire-format.json and cache-keys.json are verified by no CI anywhere
  (LAB-423, LAB-425).
- Rust keygen cell: caller-supplied keys remain N/A, but the crate
  exports an unused legacy generate_cache_key matching no protocol
  format (LAB-424); conformant keygen arrives with cachekit-rs#33.
- AAD v0x03 cells record component arity per SDK (py auto serializers
  emit the optional fifth original_type component; rs/ts/interop are
  four-component) — byte-identity code-verified per protocol#12.
- cachekit-py fleet version 0.11.1 -> 0.12.0.

Co-authored-by: multica-agent <github@multica.ai>
…aram and generate_cache_key is not unused [LAB-424]

The footnote claimed cachekit-rs delegates key construction via a
'key' parameter on #[cachekit] (no such parameter exists) and called
key::generate_cache_key 'unused' (it is called at runtime by every
#[cachekit] expansion — proc-macro call sites are invisible to grep).
Rewritten to the verified posture: get/set caller-supplied; macro
derives an SDK-internal legacy RFC-shape key, made private in
cachekit-rs#35 (LAB-424); conformant keygen arrives with #33.

Co-authored-by: multica-agent <github@multica.ai>
…on accuracy

- Correct future-dated audit stamps (2026-07-21 → 2026-07-20) in the Last-updated
  banner, Backend Abstraction audit note, and lock-id migration note.
- Fix Redis optional-capability summary to match the capability table: Python has
  both locking + TTL inspection, Rust Redis has TTL inspection only, TS Redis neither.
- Restore the documented Python backend-selection fallbacks (REDIS_URL/localhost,
  ConfigurationError on ambiguous selectors) in the SDK summary bullet.

CodeRabbit-Resolved: sdk-feature-matrix.md:9:Remove future-dated completion claims
CodeRabbit-Resolved: sdk-feature-matrix.md:115:Correct the Redis capability summary
CodeRabbit-Resolved: sdk-feature-matrix.md:194:Preserve backend-selection fallbacks
…order, TS vector wording [LAB-445]

Four clarity nits flagged by the LAB-272 expert-panel review of #24; no
factual claim changes (verdicts, marks, versions, byte constants preserved):

- Python AAD cell: 5-component is the Python norm (every auto serializer
  appends original_type); interop mode is the sole 4-component path —
  matches the line-65 callout framing instead of reading like an edge case.
- Footnotes renumbered so first appearance runs 1, 2, 3 in reading order
  (keygen note was 2 but appeared before 1); note blocks reordered to match.
- TS test-vectors cell: 'key/AAD' disambiguated to what CI actually runs —
  inline Python-generated AAD-construction + encryption (decrypt-Python-
  ciphertext) vectors. 'key' dropped: key-generation.protocol.test.ts holds
  property checks only, no Python-generated key values; key-vector coverage
  is the interop/v1 set (33 key vectors), now credited explicitly.
- AAD byte-layout detail deduped out of the matrix callout: layout, frozen
  True/False tokens, and the protocol#12 decision are already normative in
  spec/encryption.md, which stays linked — matrix keeps parity altitude.

Co-authored-by: multica-agent <github@multica.ai>
…nto lab-275-feature-gap-synthesis

# Conflicts:
#	sdk-feature-matrix.md
… [LAB-275]

Folds in the LAB-272 (protocol adherence) and LAB-273 (backend parity)
audit branches and corrects every remaining capability row against
py/rs/ts main as of 2026-07-21.

Trust-bug corrections (cells that claimed code that doesn't run):
- Key rotation: was py/rs ✅ — ships in NO SDK. core rotate_key() is a
  NotImplemented stub; py's KeyRotationState PyO3 binding has zero
  Python callers (detection-only); ts monitors nonce exhaustion only.
  Now ❌ fleet-wide with evidence footnote (LAB-516).
- Hardware-accel detection: rs ✅ → ⚠️ core-internal (never surfaced);
  ts N/A → ❌ (same Rust core, nothing exposed) (LAB-523).
- TS Arrow: 🔜 Planned → ❌ — zero code/stubs/tracking behind the
  claim (LAB-524).
- TS stampede: ✅ → ⚠️ refresh-path only; no cold-miss single-flight,
  LockableBackend unwired (LAB-519).
- Py client SWR: live in L1-only mode (ObjectCache) — backed-mode SWR
  stays dead code (LAB-388); split client/server SWR rows (ts client
  SWR was under-claimed as ❌, it is live).

New coverage: Orjson serializer row, Retry / Graceful degradation /
Cross-instance invalidation rows (py invalidation package is built but
never wired — LAB-520), and an Observability section (ts metrics
option silently ignored — LAB-517; rs has no logging/tracing and
telemetry headers report disabled by default — LAB-521).

Freshness: py 0.13.0 (interop released), rs#33 merged (interop keygen +
vectors in cargo CI), MSRV 1.85, core dep 0.3, Node 22+, wire-format +
cache-keys vector CI holes recorded closed (core#55 / protocol#26).

Co-authored-by: multica-agent <github@multica.ai>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 18 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8127d727-df14-4d6e-8192-a7f05d3a3dc3

📥 Commits

Reviewing files that changed from the base of the PR and between 442cb44 and b6d81b6.

📒 Files selected for processing (1)
  • sdk-feature-matrix.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lab-275-feature-gap-synthesis

Comment @coderabbitai help to get the list of available commands.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Heads-up from LAB-517 (cachekit-io/cachekit-ts#75): the three TypeScript Observability cells this PR adds are now stale once that PR merges.

  • Metrics / TypeScript: ❌ dead (LAB-517) → ✅ live Prometheus counters/gauges/histograms via optional prom-client peer dep (metrics: boolean | MetricsConfig, custom registry supported; loud once + no-op when prom-client missing). Footnote ¹ can drop.
  • Structured logging / TypeScript: ❌ ad-hoc console.error only (LAB-517)⚠️ pluggable error-logger hook (setLogger), default console.error — still not a structured logger (that stays a gap vs. Python's ring-buffer logger).
  • SaaS telemetry headers / TypeScript: ⚠️ user-supplied metricsProvider, never auto-wired → ✅ auto-wired from live L1/L2 hit/miss counters (user override still wins).

Whoever lands last should reconcile — happy to push the flip commit here once ts#75 merges.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Conflict in sdk-feature-matrix.md (the "Last updated" summary line — same recurring pattern as the other protocol PRs in this sweep: both this branch and main wrote a different narrative summary onto the same line). Picking one drops the other's content, so this needs a human call.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Auto-rebase attempted: merging origin/main produced 9 separate conflict hunks in sdk-feature-matrix.md — extensive overlapping edits to the same table/sections on both sides. Far beyond a safe auto-union — needs a human call. Left the branch untouched.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Auto-rebase attempted a merge of main but hit 12 conflict hunks across sdk-feature-matrix.md -- this branch's cross-SDK synthesis rewrite collides extensively with independent doc updates that landed on main since this branch was cut. Far beyond a mechanical union; needs a human to reconcile which content wins where.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Auto-rebase attempt: merged origin/main and hit 11 separate conflict hunks in sdk-feature-matrix.md — both this branch's cross-SDK feature-gap synthesis and main's intervening updates (LAB-430/446/519/595/272 etc.) rewrote overlapping rows/sections of the same shared doc. Too many competing edits to the same table to call this a safe non-overlapping union; resolving would mean picking one side's audit findings over the other's in multiple places. Needs a human to reconcile. Merge aborted, branch untouched.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Merge conflict against main in sdk-feature-matrix.md — 12 separate conflict blocks across the doc (SDK overview, backends, backend abstraction, reliability, developer experience, protocol compliance, architecture notes). This is the same shared-file conflict pattern as the sibling protocol matrix-update PRs stacked on main right now; resolving it correctly means reconciling a dozen concurrently-edited table sections, several with directly conflicting factual claims (not just additive rows). Not a safe automated call — needs a human to merge these matrix-update branches in sequence.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Auto-rebase sweep: merging main into this branch conflicts in sdk-feature-matrix.md. Both this branch (LAB-275 feature-gap synthesis) and main made extensive, overlapping edits to the same table rows and changelog blurb with contradictory content — not a non-overlapping union. Needs a human call on which edits win/how to fold the two synthesis passes together.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Auto-rebase check: base main has moved (main HEAD 936f22f) and this branch now conflicts in sdk-feature-matrix.md — 8+ separate hunks across the capability matrix disagree with main's newer content (main has since had LAB-595/LAB-519/LAB-272/etc. land on the same rows). Reconciling requires picking/merging matrix cells by hand — needs a human call, not safe to auto-resolve.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Auto-rebase attempted: merging main into this branch produces 12 conflict hunks across sdk-feature-matrix.md — overlapping table-cell edits and banner-text edits with different facts on each side (this branch's synthesis vs. main's independent updates, e.g. LAB-729/LAB-430/LAB-446/LAB-595/LAB-519/LAB-272 entries). Not a safe non-overlapping union; resolving would mean picking one side's content over the other's in several places. Left for a human call.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Auto-rebase attempted a merge from main but hit heavy content conflict in sdk-feature-matrix.md (12 separate hunks) — same shared table this branch and several siblings both edit with differing factual claims per cell. Not a non-overlapping union; resolving means picking one side's data over the other's across many rows. Needs a human call. Left branch untouched (no push).

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Merge conflict against main in sdk-feature-matrix.md — left unresolved, needs a human call.

14 separate conflict hunks across the feature matrix table and its provenance notes: both this PR (LAB-275 cross-SDK feature-gap synthesis, folding in LAB-272/LAB-273) and main (LAB-903/LAB-998/LAB-729/LAB-430/LAB-446/LAB-595/LAB-519 shipped since) rewrote large, overlapping sections of the same document with different facts. Resolving would mean re-deriving which matrix cells are current across dozens of rows — not a safe auto-merge or a non-overlapping union. Left the branch untouched (merge aborted, nothing pushed).

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Attempted auto-rebase onto main — 15 conflict blocks in sdk-feature-matrix.md, same doc-drift pattern as sibling matrix PRs: this branch's code-verified refresh disagrees with main's own newer entries across many rows/footnotes. Resolving means picking one side's facts over the other's in bulk — needs a human call (likely: rebase this whole batch of matrix PRs together rather than one at a time).

… state (LAB-1397)

cachekit-ts#75 (LAB-517) merged 2026-08-03, wiring the TS metrics option
live. The Metrics cell goes dead→live prom-client, SaaS telemetry headers
go user-wired→auto-wired, structured logging goes ❌→⚠️ setLogger hook
(still not a structured logger). Footnote ¹ documented the silent no-op
trust bug that ts#75 fixed, so it drops. Cell text mirrors the
reconciliation comment on protocol#29 by the ts#75 implementer.
@27Bslash6

Copy link
Copy Markdown
Contributor Author

Flip landed: b6d81b6 updates the three TypeScript Observability cells to the shipped post-cachekit-ts#75 state (Metrics ✅ live prom-client, structured logging ⚠️ setLogger hook, SaaS telemetry headers ✅ auto-wired) and drops the now-stale footnote ¹. Tracked as LAB-1397. This closes the reconciliation offer above.

@kodus-27b

kodus-27b Bot commented Aug 3, 2026

Copy link
Copy Markdown

Kody Review Complete

Great news! 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Superseded by #48 (LAB-1400), which consolidates the ten open sdk-feature-matrix.md PRs into one code-verified end-state.

Partly incorporated — this PR carried the single most valuable finding in the set, and it was still true: key rotation ships in no SDK (rotate_key() returns NotImplemented, cachekit-core src/encryption/core.rs:492). decisions/key-rotation.md on main cited this PR as the fix for the rotation row, so a merged decision record pointed at an unmerged PR while the matrix went on claiming py ✅ / rs ✅. #48 fixes both.

Also incorporated: the hardware-acceleration correction, Orjson/Arrow rows and extras, ts Arrow 🔜 → ❌, the Observability section, the Retry / Graceful-degradation / Cross-instance-invalidation rows, and rs MSRV 1.85.

Rejected as stale — each superseded by work that shipped after this PR was authored: versions (py 0.13.0, ts 0.1.2 → now 0.17.1 / 0.1.5), ts Workers ❌ (shipped, LAB-595 + LAB-750), rs stampede ❌ (shipped, LAB-518), ts cold-miss single-flight ⚠️ (shipped, LAB-519), rs Redis/Workers locking ❌ (shipped, LAB-426), interop "merged unreleased" (released in all three, LAB-998), and the Backend Abstraction section (already on main).

One correction to the PR's own wording: it says py's KeyRotationState PyO3 binding "has zero Python callers — only mismatch detection is live". The binding is not merely uncalled, it is callablestart_rotation() returns success and is_rotating() then reports True while nothing in src/cachekit/ consults it (rust/src/python_bindings.rs:258, registered :394). #48 warns operators off it explicitly, since taking that success for a grace window and swapping the master key loses every encrypted entry.

@27Bslash6 27Bslash6 closed this Aug 3, 2026
@27Bslash6

Copy link
Copy Markdown
Contributor Author

Merge conflict against main in sdk-feature-matrix.md — 15 conflict hunks across the shared living feature-matrix doc, both sides editing overlapping cells/sections. Resolving means picking one side's data over the other's, not a safe union. Needs a human call.

27Bslash6 added a commit that referenced this pull request Aug 4, 2026
… one code-verified refresh (LAB-1400) (#48)

* docs(matrix): consolidate ten conflicting matrix PRs into one code-verified refresh (LAB-1400)

Ten open PRs all edited sdk-feature-matrix.md; every merge invalidated the
other nine. Sequencing them costs more than authoring the end-state once, so
this regenerates the matrix from current SDK code rather than unioning the
stale diffs — several PRs describe SDKs that have shipped further since July.

The matrix is a trust surface (LAB-388, LAB-998), so the point of doing this
from code is the falsehoods it surfaces. Three cells on main claimed
capabilities no code supports:

- Key rotation py/rs ✅ — rotate_key() returns NotImplemented
  (cachekit-core src/encryption/core.rs:492). This repo's own
  decisions/key-rotation.md already said ❌ fleet-wide and cited protocol#29
  as the fix; #29 never merged, so a merged decision record pointed at an
  unmerged PR while the matrix kept lying.
- cachekit-rs ::secure preset — does not exist; the preset is ::encrypted and
  secure() is a post-build accessor that errors without encryption.
- cachekit-rs sync support ✅ — every op is async fn; the macro only expands
  on async functions.

Also corrected against code and the registries: py no builder API, hardware
acceleration surfaced by Python only, rs MSRV 1.85 (not 1.82), ts Node 22+
(not 20+), py 0.17.1, ts 0.1.5, rs feature-flag list. Recorded a new instance
of the same class: py's max_retries config fields have zero consumers.

New rows and sections carried over from the folded PRs: Retry, Graceful
degradation, Cross-instance L1 invalidation (LAB-520), client-L1 SWR
(LAB-728), Orjson serializer, Observability (LAB-275). Flipped on verified
evidence: rs circuit breaker / retry / degradation / cold-miss single-flight
(LAB-518), rs Workers locking + TTL (LAB-426), ts SWR on Workers via
withExecutionContext -> ctx.waitUntil (LAB-751).

spec/wire-format.md now names both CI enforcement points for the envelope
vectors (LAB-423, protocol#25): this repo's verify.yml and cachekit-core's
sha256-pinned tests/wire_format_vectors.rs.

Supersedes protocol#25 #28 #29 #31 #32 #33 #35 #37 #40 #43.

* docs(matrix): apply expert-panel findings — release-state, fail-open, LAB-513 (LAB-1400)

Expert panel (bug-hunter / security / craftsman / pragmatism, high stakes)
contradicted four claims in the first commit. All four verified before fixing.

Release state vs branch state — the panel caught this commit committing the
very bug the ticket exists to fix. Tag cachekit-rs-v0.5.0 is 494d578
(2026-07-25) with default = [cachekitio, encryption, l1], no reliability
feature, and no reliability.rs or flight.rs in src/. LAB-518/728/729 all
landed after it, so six rs cells were ✅ for code no `cargo add` can reach.
They are now 🚧 with footnote 13 naming the tag and the missing files. This
also corrects main's pre-existing LAB-729 backpressure ✅.

Security corrections, all verified against code:
- cachekit-py DOES ship a live PyO3 KeyRotationState
  (rust/src/python_bindings.rs:258, registered :394). start_rotation()
  returns success and is_rotating() then reports True while nothing consults
  it — an operator who reads that as a grace window and swaps the master key
  loses every encrypted entry. The previous footnote said no binding exists.
- Python's encrypted read path is fail-OPEN by default
  (EncryptionWrapper fail_closed=False, encryption_wrapper.py:113; settings.py
  :225), not fail-closed as documented. On fingerprint mismatch or AES-GCM auth
  failure the default is warn + recompute. Anyone using hard errors as their
  wrong-key or tamper alarm needs CACHEKIT_ENCRYPTION_FAIL_CLOSED=true.
- LAB-513 is real and now asserted rather than declined: ts `secure` is an
  unconditional alias, `secure = { wrap: (fn, o) => this.wrap(fn, o) }`
  (cache-core.ts:832), every intent is typed SecureCache (cache.ts:87), and
  encryption applies only `if (this.encryption)` (:486) — so secure.wrap on a
  non-secure() cache stores plaintext silently. Declining to assert it was the
  wrong call when two greps settle it.

Further accuracy fixes: three of four rs presets need the non-default `redis`
feature (only ::io builds on defaults); py SWR also requires an explicit ttl=
(wrapper.py:666) which presets never set; max_retries has no *operational*
consumer (one no-op validator branch at settings.py:252) rather than zero
reads; ts 0.1.5 shipped core 0.4.0 and LAB-751, so the rollout note no longer
contradicts the version table; footnote 3 was missing file-backend.json; ts
metrics are Node-only, CI-excluded from the Workers bundle.

Trimmed per the pragmatism filter: banner cut to a date plus a reversed-cells
list, footnote 8 and two audit-narrative paragraphs deleted, self-defense
clauses removed. Added the CHANGELOG entry the banner's pointer promised.

Rejected: relocating the pre-existing backpressure and Workers footnote essays
to decisions/, and deleting the pre-existing wasm bundle-size figures — all
pre-existing content outside this ticket's scope (matrix end-state + queue
cleanup); mixing a prose refactor into an accuracy fix would obscure both.

* fix: address coderabbit review — matrix date, unreleased Rust SWR qualifier, blockquote lint, re-encode scope

CodeRabbit-Resolved: sdk-feature-matrix.md:9:Correct the audit date before
CodeRabbit-Resolved: sdk-feature-matrix.md:71:Remove the blank quoted lines
CodeRabbit-Resolved: sdk-feature-matrix.md:301:Mark the Rust SWR capability a
CodeRabbit-Resolved: spec/wire-format.md:41:Correct the `cachekit-core` re

* fix(matrix): re-verify every version claim against published artifacts (LAB-1400)

The expert panel found the previous pass had introduced two new false cells of
the exact class it existed to remove. Both confirmed against published
artifacts, not branches, and both fixed.

B1 — TypeScript has NOT shipped the protocol-1.1 bin flip, on either path.
The previous revision claimed ts 0.1.5 ships it and that core-wasm@0.1.2 pins
core 0.4.0 so Workers carries it too. Verified from npm: @cachekit-io/cachekit
0.1.5 (published 11:15Z) has dependency pins byte-identical to 0.1.4's — exact,
caret-free pins on cachekit-core-ts@0.1.2 and cachekit-core-wasm@0.1.1. All
five platform .node binaries in core-ts@0.1.2 embed cachekit-core-0.2.0;
core-wasm@0.1.1 embeds 0.3.0. core-wasm@0.1.2 does embed 0.4.0 but published
at 14:28Z, 3h13m AFTER ts 0.1.5, so no published cachekit pins it. ts#91 bumped
the source pin without republishing the addon it consumes.

Scoped the consequence correctly: this is intra-SDK rollout skew, NOT a
cross-SDK interop break. protocol#11 makes auto-mode bytes SDK-internal, so py
writing bin cannot break a ts reader. The panel's framing overreached there.

B2 — the Rust reliability tier IS released and on by default. cachekit-rs
0.6.0 published 2026-08-03T14:58:16Z, 74 minutes before the previous pass's
final commit, whose message added an "unreleased" qualifier. Verified inside
the published .crate: src/reliability.rs, src/flight.rs,
tests/reliability_tests.rs, get_with_swr in src/l1/mod.rs, and
default = [cachekitio, encryption, l1, reliability]. Six cells flipped 🚧 → ✅;
footnote 13 now records the release and why the qualifier outlived its truth.
0.6.0 also resolves core 0.4, so rs now carries the bin flip.

B3 — SDK Overview versions are floors. CodeRabbit asked for this and I
rebutted it; the panel overturned the rebuttal and both were right. Footnote 4
said "version cells are floors" with no table qualifier, and rs sat at 0.5.0
against a published 0.6.0 on the day the doc stamped itself current.

Root cause was structural, not careless: the matrix was regenerated from main
against a registry snapshot that went stale before the final commit landed.
Recorded the method in decisions/matrix-version-verification.md — registry
metadata establishes which artifact is current, and where an embedded
dependency decides the claim, the .crate/.tgz gets opened. Added
tools/check-version-floors.py to verify.yml: fails on a bare X.Y.Z in the SDK
Overview table. Scoped to that table on purpose — versions elsewhere are exact
facts about specific artifacts and are correctly bare. Verified it passes clean
and fails on the reintroduced 0.5.0.

Also from the panel:
- Footnote namespace repaired. Markers 1-4 were each defined TWICE with
  unrelated content, so half the evidence pointers resolved to the wrong note —
  including the floors note B3 depends on. Protocol Compliance is now 14-17;
  every marker defined exactly once (17 total).
- The two buried security facts are now Encryption table rows: the py
  fail-OPEN-by-default tamper/wrong-key mode, and secure-API enforcement
  carrying the LAB-513 cache.secure.wrap alias (CWE-311). While writing the
  first row I nearly asserted rs and ts both "fail closed" unverified — checked
  instead: rs propagates (client.rs:143), ts propagates from getEntry but its
  wrap() degradation is a bare `catch {}` with no error-class check
  (reliability/degradation.ts:13), so a decrypt failure degrades to an uncached
  recompute wherever degradation is on.
- spec/wire-format.md's implementation-status note no longer contradicts the
  matrix; it now names rs 0.6.0 and the ts embedded-core reality.
- The banner promised per-cell evidence "in the PR body" that wasn't there. The
  per-PR fold ledger now lives in CHANGELOG.md, which the banner points at.

All seven local checks green: five vector suites, frame-crosscheck, and the new
floors guard. No test-vector or normative bytes changed.

* fix(matrix): correct an inverted TypeScript fail-mode claim (LAB-1400)

Second panel round on the remediation. Two panelists independently landed the
same CRIT: the TypeScript cell I added in 0aaf298 was the inverse of the code.
Verified before changing anything.

I wrote "propagates from getEntry, but wrap() degradation is a bare catch{}
wherever degradation is on". Wrong in both halves:

- getEntry does not propagate — it RETURNS this.run('get', ...)
  (cache-core.ts:405) with the decrypt inside the callback (:415), so an
  EncryptionError from a tampered payload or a wrong key travels into
  ReliabilityExecutor.execute -> withDegradation and is eaten by the bare catch
  (reliability/degradation.ts:13). get() returns null, a silent miss; through
  wrap() the function is re-executed and the result RE-STORED. Retry and the
  circuit breaker both rethrow, so degradation is the only swallower.
- "wherever degradation is on" reads conditional; it is unconditional on every
  encrypted preset. degradationEnabled = config.degradation !== false
  (reliability/executor.ts:39), and secure()/production()/io() all pass
  degradation: true (intents-core.ts:186). Only minimal sets false, and minimal
  carries no encryption.
- There is no failClosed option anywhere in cachekit-ts — zero hits under
  packages/cachekit/src. Python's CACHEKIT_ENCRYPTION_FAIL_CLOSED has no ts
  counterpart, so the only lever is reliability: { degradation: false }, which
  also gives up backend-outage degradation.

Net effect: I had presented TypeScript as safer than Python on this row when it
is strictly less remediable. Now stated as fail-OPEN by default and NOT
switchable, with the mechanism in new footnote 8. That footnote also closes the
numbering gap left by an earlier deletion, so markers are contiguous 0-17.

Also from the panel, each verified:
- client.rs:143 was not a decrypt site — it is `impl PartialEq for SwrToken`. I
  had read an awk-offset as a file line. Real propagation sites are client.rs:830
  and :847. The claim was true; the pointer was falsifiable, which rule 5 of the
  new decision record exists to prevent.
- The rs fail-closed mechanism was misattributed to SecureCache, which contains
  no degradation logic. The gate is the macro suppressing fail_open_arm under
  args.secure (cachekit-macros/src/lib.rs:439-451).
- Footnote 13 overclaimed "no feature flags": macro-level graceful degradation
  and the automatic single-flight wiring come from the proc-macro, and
  macros = ["dep:cachekit-macros"] is not in default. Circuit breaker, retry,
  backpressure and L1 SWR are genuinely default-on; those two need
  --features macros.
- My footnote renumber missed one reference — the pattern required "(" directly
  before "see", and one site reads "— see ... note 2)". It still pointed at the
  DynamoDB note, the exact defect the renumber existed to remove. Now 15.
- Bare npm package names (cachekit-core-ts@0.1.2, cachekit-core-wasm@0.1.1) do
  not resolve on npm and contradicted the scoped form already used in note 1.
  Scoped to @cachekit-io/... in both the matrix and spec/wire-format.md.
- The ts Graceful-degradation cell was still an unqualified ✅ while py and rs
  carried their caveats — the same burial this work is fixing. Now flags that it
  catches every error class and points at the new row.

Panel confirmed clean: every published-artifact claim in the rollout table and
footnote 13, the interop-mode scoping (interop values are plain MessagePack with
no envelope, so the bin flip genuinely cannot cross SDKs — verified against
wire-format.md and interop-mode.md, not asserted), CWE-311 as the right class for
the secure.wrap alias, footnote 5's rotation warnings surviving the renumber, and
check-version-floors.py both ways.

Six vector/crosscheck suites plus the floors guard green. No test-vector or
normative bytes changed.

* fix(matrix): scope floor semantics to the SDK Overview table (LAB-1400)

CodeRabbit was right that "every version in the matrix is a floor" is too
broad. The document deliberately carries bare, exact versions elsewhere --
an embedded `cachekit-core-0.2.0` read out of a `.node` binary, a caret-free
npm pin -- and those are artifact evidence under rule 2, not floors. The
blanket wording reclassified them as floors, which would make them false,
and invited a future reader to "fix" them by appending `+`. Both sites now
say what the CI guard actually enforces, and say why the rest stay bare.

Also renames the `l` loop variable flagged by ruff E741. Uses `raw` rather
than the suggested `text_line` so the line stays under 88 chars without a
wrap, and to avoid shadowing the enclosing `text` parameter.

* fix(matrix): correct my own floor-scoping regression (LAB-1400)

cdbedba scoped floor semantics to the SDK Overview table, which CodeRabbit
was right to ask for -- but the sentence I wrote to explain the rest of the
document said bare versions elsewhere are "deliberate ... do not correct
them by adding `+`". Read literally that is an instruction to STRIP the
seven floors that legitimately live outside that table: the Compliance
Status interop row (`PyPI 0.14.0+`, `crates.io 0.4.0+`, `npm 0.1.3+`, all
governed by note 17), note 13's `cachekit-rs 0.6.0+`, and the Architecture
Notes release bullets. Following it reopens the LAB-998 class -- a wrong
interop ship-status version -- with no CI enforcement, since the guard
polices only the Overview column.

Two expert panelists found this independently. It is the same mechanical
failure class the PR exists to close, committed by the commit meant to
close it, which is now the fifth instance in this file.

All three sites now state the real rule: a version answering "which release
do I need" is a floor, a version that is evidence about one specific
artifact stays bare, and neither is mechanically convertible into the
other. The checker docstring no longer claims everything elsewhere is bare.

* fix(matrix): settle the dual-read question by test; make the gate honest (LAB-1400)

The previous run escalated two questions to Ray and left four determinate
findings unfixed. Both questions turned out to be answerable here, and all four
are fixed. Withdrawing that escalation.

Q1 (dual-read) is settled by an executed test, not a judgement call. The claim
"a reader built against core <= 0.3.0 rejects bin" is FALSE.
cachekit-core/tests/dual_decode.rs is the canonical implementation's permanent
CI proof: assert_all_readers_decode (:69) deserializes wire into
StorageEnvelopeLegacy — a plain Vec<u8>, explicitly "the shape every pre-writer-
flip SDK shipped" — and dual_decode_matrix_against_bin_vectors (:161) runs it
over every *_bin twin, with bin8/bin16/bin32 width tiers. Legacy readers accept
bin. It also matches CHANGELOG.md ("Not a breaking change — dual-read is mutual")
and wire-format.md's own verified compatibility table, both of which the false
claim contradicted.

So the fleet-upgrade sequencing advice derived from it — "ts instances still on
0.1.2 will meet bin envelopes and reject them; sequence the republish before any
fleet upgrade" — was a manufactured migration risk. Removed. The ts core lag is
now stated as what it is: a forgone size saving on ts's own writes, with nothing
to sequence. spec/wire-format.md:104 corrected to match.

Q2 (the gate) needed a rewrite, not a decision. Reproduced two of the reported
defects: an empty version cell PASSED, and a valid backticked floor `0.6.0+` was
REJECTED. Rewrote the parser to key off the header row's Version column, treat a
GFM separator row as one whose every cell matches ^:?-{3,}:?$, strip markdown
emphasis before validating, and fail closed when the table cannot be located or
parsed — a guard that silently checks nothing is worse than none. Added
tools/test_check_version_floors.py, a 15-case mutation suite covering every leak
the panel reported plus the false positives; it runs before the guard in CI so
the checker cannot degrade to reporting OK. 15/15.

Also made the gate stop overclaiming. verify.yml and the decision record both
said it encoded "the one failure mode that recurred four times". It catches ONE
of the six incidents — the rs 0.5.0 snapshot in the Overview table. LAB-388 (a
tick on dead code), LAB-998 (a ship-status boolean) and the footnote regressions
are invisible to it. An overclaiming gate converts a known gap into an
assumed-safe one.

The four determinate findings, each verified first:

- ts key-rotation cell promised nonce-exhaustion detection the caller cannot
  observe. NonceExhaustedError is raised inside encrypt(), which sits inside the
  set callback, so withDegradation absorbs it. On the one condition where
  continuing to encrypt risks GCM nonce reuse, nothing reaches the application.
- footnote 8 was scoped read-only while the identical mechanism swallows writes:
  setEntry returns this.run('set', ...) with encrypt inside (cache-core.ts:478,
  :487), so an encrypt/NAPI/key failure stores nothing while resolving as
  success and every later wrap() re-executes origin forever.
  Rather than patch a third path, footnote 8 now states the general mechanism
  once — the executor wraps every operation, degradation catches every error
  class — and derives read, write and nonce-exhaustion from it. Three rounds
  each patched one path of one mechanism; this states the mechanism.
- rs `workers` was an unqualified tick that cannot compile as written. The
  published 0.6.0 crate has default = [cachekitio, encryption, l1, reliability]
  and src/lib.rs carries compile_error! for workers x l1 AND workers x
  reliability, so `cargo add cachekit-rs --features workers` trips both; it needs
  --no-default-features. New footnote 19, referenced from both Workers cells and
  the two Backend Abstraction rows that credit rs Workers capabilities.
- footnote 5 misdirected on the file's most safety-critical reversal: three
  cells cited 5 for Python's fail-open encrypted read path, but 5's numbered
  sentence is "Key rotation ships in no SDK" and the fail-open evidence sat in an
  unnumbered paragraph. Split out as footnote 18.

Added rule 6 to the decision record, which is the lesson three of the six
incidents share: for a behavioural claim, cite the executed test rather than a
mechanism you traced. Where none exists, trace the whole path including its
error handling and say which layer you checked.

Footnotes: 20 markers, each defined exactly once, all referenced. Seven
vector/crosscheck suites plus the floors guard and its mutation suite green. No
test-vector or normative bytes changed.

* fix(matrix): stop inventing a crypto risk; close two checker gaps (LAB-1400)

Fourth panel round. It CONVERGED on the headline items — two panelists
independently confirmed the dual-read reversal is correct (core 0.3.0's
compressed_data really is a bare Vec<u8>, byte-identical to
StorageEnvelopeLegacy; both lagging ts artifacts lock rmp-serde 1.3.1; checksum
never enters the flip; the size guards are length-identical) and confirmed
footnote 19's compile_error! facts against the published crate. It then found
two new CRITs, both mine, both security, both in the OVERSTATING direction this
time.

CRIT — I invented a GCM nonce-reuse risk that cannot happen. footnote 8 said
nonce exhaustion is the condition "where continuing to encrypt risks GCM nonce
reuse". The core fails closed: generate_nonce() returns NonceCounterExhausted
once counter >= u32::MAX, and the counter is AtomicU64 *specifically* so it
stays exhausted rather than wrapping — the doc comment at
cachekit-core/src/encryption/core.rs:168-185 spells out that AtomicU32 would
wrap and cause reuse, which is why it is u64. Reuse is unreachable. The real
unobservable consequence is that every encrypted set() on that encryptor
silently stores nothing for the rest of the process's life, and an operator
seeing only a rising miss rate would reach for a master-key rotation that ships
nowhere and destroys every entry. Corrected, and footnote 5's ts sentence
corrected with it — it had said the missing signal is the one telling an
operator "rotation is now mandatory", which is the wrong remedy.

CRIT — footnote 19 recommended an invocation that silently disables encryption.
It said the Workers build is `--no-default-features --features workers`, adding
encryption "if wanted". With the feature off, CacheKitBuilder::encryption() and
::encryption_from_bytes() compile to stubs returning Ok(self)
(client.rs:1019-1032), so the documented builder call succeeds, nothing errors,
and the cache stores plaintext at rest. Only secure() is cfg-gated and fails
loudly. The recommended invocation now includes encryption, the no-op stub is
called out as CWE-311 (same shape as the LAB-513 row six lines above), and the
Encryption table's Rust column cites 19 so its ticks carry the precondition.

Also from the round:
- The tamper row's ts cell said "fail-OPEN ... reads and writes", which reads as
  "stores plaintext on encrypt failure" — the throw precedes backend.set, so
  nothing is stored. Now "fail-OPEN on reads, silently drops writes".
- The ts key-rotation cell had lost its 5 citation in the last commit, leaving
  the ts column's only rotation pointer aimed at a note with no rotation
  content. Cites 5 and 8.
- footnote 18 attributed both fingerprint mismatch and AES-GCM auth failure to
  EncryptionWrapper(fail_closed=False); that flag gates the fingerprint
  pre-check, and the auth-failure policy is handle_decrypt_failure
  (cache_handler.py:1307/:1317, resolved :550-552). Conclusion was right, the
  citation was half-right. Both cited now.
- footnote 8's opening claim "*every* cache operation runs inside
  ReliabilityExecutor.execute" was over-general — L1 hits short-circuit before
  it and acquireLock sits outside. Scoped to operations that reach the backend.
  Worth noting the direction: having failed twice by being too narrow about this
  mechanism, I overshot into too broad.
- check-version-floors.py rejected a floor carrying this document's own
  footnote markers (0.6.0+¹⁷ reported as "a bare snapshot"), and silently
  skipped a second table under the same heading. Both fixed — superscripts are
  stripped with the other decoration, and a second table now fails closed
  rather than reporting OK on a section it cannot account for. Mutation suite
  15 -> 17 cases, both gaps covered, 17/17.
- The "catches one of six incidents" arithmetic did not add up in the decision
  record or verify.yml (1 + 4 invisible = 5). Now stated as one facet of one
  incident, with the other five itemised.

Footnotes: 20 markers, each defined once, all referenced. Seven vector suites
plus the guard and its 17-case suite green. No test-vector or normative bytes
changed.

* style(tools): escape ambiguous dash literals in the floors checker (LAB-1400)

CodeRabbit's post-d488c35 round, both threads (ruff RUF001). Accepted — the
readability point is specific to this code, not a blanket rule:

    PLACEHOLDER = re.compile(r"^[—–-]{1,3}$")

That character class holds U+2014 EM DASH, U+2013 EN DASH and U+002D
HYPHEN-MINUS, which are visually indistinguishable in a monospace diff, so a
maintainer cannot tell what the class actually matches. Now written with \u
escapes and a comment naming all three, consistent with the DECORATION regex
which already used escapes. Same for the en-dash test fixture and PHP_ROW's
em dashes.

Also reworded the two f-string error messages that carried a prose em dash, so
both files are RUF001-clean at source level rather than clean-except-for-two.
Verified by tokenising the raw source (RUF001 reads source text, not
AST-resolved values — an early check of mine conflated the two and reported
false hits): 0 ambiguous characters in code string literals across both files.
The two remaining live in a docstring and a comment, which are RUF002/RUF003
scope and were not selected.

Note ruff is not configured in this repo (no pyproject/setup.cfg, no ruff step
in verify.yml), so nothing enforced this; it is accepted on merit and to stop
the finding recurring.

Behaviour is unchanged and proven so: the 17-case mutation suite still passes
17/17, including the em-dash and en-dash placeholder cases that exercise exactly
the escaped class. Seven vector/crosscheck suites plus the guard also green. No
matrix, spec, test-vector or normative bytes touched.

While in the file, aligned the checker docstring's incident arithmetic with the
decision record (it still said the guard catches one of six with an enumeration
summing to five; it catches one facet of one).

---------

Co-authored-by: opus <ray.geo30@insighttimer.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants