Skip to content

feat: content-addressed model cache + PR area auto-labeling - #227

Open
albin-george-kurian wants to merge 7 commits into
OpenAgentHQ:mainfrom
albin-george-kurian:feat/auto-label-cache-dedup
Open

feat: content-addressed model cache + PR area auto-labeling#227
albin-george-kurian wants to merge 7 commits into
OpenAgentHQ:mainfrom
albin-george-kurian:feat/auto-label-cache-dedup

Conversation

@albin-george-kurian

Copy link
Copy Markdown
Contributor

Summary

Implements two issues: the filesystem cache now stores model weights content-addressed, so identical weights are never duplicated on disk, and pull requests are automatically labeled by the areas they touch.

Closes #59
Closes #137

Changes

Content-addressed cache (#59)

  • src/modeldock/adapters/cache/filesystem.py — weights now live in a blob store at cache/blobs/<sha256[:2]>/<sha256> and are exposed at their readable cache/models/<name>/<tag>.gguf path via a hard link (copy fallback where hard links are unsupported). store_blob() discards a duplicate download and returns the existing blob; a digest that does not match the catalog's is rejected before anything enters the store. Blobs are reference-counted through the manifest, so evict() never deletes weights another ref still uses, and clean() reclaims orphaned blobs (force=True wipes them).
  • src/modeldock/ports/cache.py — new ContentStorePort protocol (has_blob, blob_path, store_blob, link_into, record_artifact), deliberately separate from CachePort so manifest-only caches remain valid implementations and callers feature-detect the blob API.
  • src/modeldock/core/manager.py_install_via_http() skips the download entirely when the catalog publishes a digest whose bytes are already stored (instant, offline-capable re-install); otherwise it downloads, stores, links and records. Caches that are not a content store keep the previous behaviour unchanged.
  • src/modeldock/ports/__init__.py — exports ContentStorePort.

PR auto-labeling (#137)

  • .github/labeler.yml — path globs mapping changed files to area: cli / core / adapters / docs / tests / ci.
  • .github/workflows/labeler.ymlactions/labeler@v5 on pull_request_target (so PRs from forks get a token that can write labels) with contents: read, pull-requests: write, issues: write. Checks out no PR code, which is what keeps that trigger safe. sync-labels: false, so manually applied labels are never stripped. A bootstrap step creates the area labels on first use and leaves existing ones exactly as maintainers configured them.

Docs

  • Architecture.md §8 — documents the real layout (blobs/ + hard link), the new blob/path manifest fields, the skip-the-download path, and reference-counted reclamation; module tree and layer table updated.
  • docs/user-guide/cache.md — user-facing explanation of the blob store and what it means for disk usage.
  • CHANGELOG.mdUnreleased entries under Added/Changed.

Testing

  • pytest697 passed, 4 skipped (skips are the Ollama integration tests; no local Ollama available).
  • New tests/unit/test_content_store.py (29 tests): identical bytes → one blob; digest mismatch rejected with nothing entering the store; hard-link fallback to copy; a shared blob survives one eviction and is reclaimed on the last; orphan pruning; and at manager level — two models with the same published digest produce 1 download, 1 blob, 2 usable artifact paths.
  • New tests/unit/test_labeler_config.py (40 tests): required areas present, every glob still matches a real path in the tree, per-area routing table, areas do not overlap, and every configured label is bootstrapped by the workflow.
  • tests/unit/test_port_contract.pyContentStorePort added to the shared adapter contract suite.
  • tests/unit/test_workflow_permissions.pyTestLabelerWorkflow: exact permission block, job inherits top level, trigger is pull_request_target, and no actions/checkout.
  • Quality gates: ruff check src tests, ruff format --check src tests, mypy src (strict, 80 files), bandit -r src -c pyproject.toml — all clean.
  • Labeler verification: pull_request_target runs the workflow from the base branch, so this PR does not label itself. After merge, verify on a test PR touching src/modeldock/cli/app.py + docs/index.md → expect area: cli + area: docs. test_labeler_config.py is the pre-merge proof that the mapping is correct.

Checklist

  • Branch named per Git Workflow (feat/auto-label-cache-dedup)
  • Not developed on main
  • Code follows AGENT.md coding standards (type hints, Pydantic v2, no generic Exception, no business logic in CLI)
  • domain/ and ports/ stay pure (no I/O, no framework imports)
  • Quality gates pass locally: ruff, mypy --strict, bandit, pytest
  • Docs updated if behavior changed (Architecture.md §8, docs/user-guide/cache.md, CHANGELOG.md)
  • pyproject.toml and src/modeldock/__init__.py versions match (no version change in this PR)

…hts are stored by SHA-256 and hard-linked to their readable path, so identical content is never duplicated and a known digest skips the download. Adds path-based PR labels (cli/core/adapters/docs/tests/ci)." -m "Closes OpenAgentHQ#59" -m "Closes OpenAgentHQ#137
Comment thread src/modeldock/ports/cache.py Fixed
Comment thread src/modeldock/ports/cache.py Fixed
Comment thread src/modeldock/ports/cache.py Fixed
Comment thread src/modeldock/ports/cache.py Fixed
Comment thread src/modeldock/ports/cache.py Fixed
Comment thread tests/unit/test_labeler_config.py Fixed

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.


Graphify review — findings

Adds a content-addressed blob store to FilesystemCache: downloaded weights land at cache/blobs/<sha256[:2]>/<sha256> and are exposed at cache/models/<name>/<tag>.gguf via a hard link (copy fallback where links are unavailable), so byte-identical weights cost disk once and are reference-counted — evict() reclaims a blob only when its last referencing entry is gone and clean() prunes unreferenced blobs. Introduces ContentStorePort as an optional companion to CachePort (has_blob, blob_path, store_blob, link_into, record_artifact) so manifest-only caches stay valid, and makes ModelManager.install() skip the download entirely when the catalog publishes a variant's sha256 and those bytes are already stored. Adds a pull_request_target PR labeler that tags PRs by touched area (cli/core/adapters/docs/tests/ci) additively, ensuring the area labels exist first without overwriting manually curated ones.

Worth a look

  • Privileged pull_request_target workflow uses mutable action tags.github/workflows/labeler.yml:42 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • link_into may create hardlink to shared blob, mutable via readable pathsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • link_into must not consume the source blobsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • store_blob return contract changed from name-keyed to content-keyedsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • TOCTOU between _prune_orphan_blobs and store_blob deletes freshly stored weightssrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 467 functions depend on the 380 functions this change touches.

Health — this change adds coupling hotspots:

  • new: ModelManager — 34 callers, 25 callees
  • new: _manager() — 21 callers, 3 callees
  • new: _manager() — 10 callers, 3 callees
  • new: _manager() — 5 callers, 3 callees

Verification — 467 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 467 function(s) in the blast radius were not formally verified this run

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

)


def _manager(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_manager()

high coupling complexity (Ca·Ce = 15).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.


Graphify review — findings

Adds a content-addressed blob store to FilesystemCache: downloaded weights land at cache/blobs/<sha256[:2]>/<sha256> and are surfaced at cache/models/<name>/<tag>.gguf via a hard link (copy fallback where links are unavailable), so byte-identical weights are stored once and reference-counted through new manifest blob/path fields; the blob operations live behind a separate optional ContentStorePort so manifest-only caches stay valid. Makes ModelManager.install() skip the download entirely and just link when the catalog publishes a variant's sha256 and those bytes are already stored, and reworks evict() to unlink and reclaim a blob only once its last referencing entry is gone while clean() prunes unreferenced blobs. Adds a pull_request_target labeler workflow that applies area: labels from changed paths (additive, sync-labels off) and ensures the label set exists idempotently.

Worth a look

  • Concurrent manifest records can overwrite each othersrc/modeldock/adapters/cache/filesystem.py:91 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Reused orphan blob can be pruned before it is recordedsrc/modeldock/adapters/cache/filesystem.py:225 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Manifest path can unlink files outside cachesrc/modeldock/adapters/cache/filesystem.py:302 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • _gc_blob accepts malformed digests that can unlink files outside the blob storesrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • store_blob then clean race: fresh blob only protected by mtime grace windowsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 475 functions depend on the 388 functions this change touches.

Health — this change adds coupling hotspots:

  • new: ModelManager — 34 callers, 25 callees
  • new: _manager() — 21 callers, 3 callees
  • new: _manager() — 10 callers, 3 callees
  • new: _manager() — 5 callers, 3 callees

Verification — 475 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 475 function(s) in the blast radius were not formally verified this run

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

)


def _manager(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_manager()

high coupling complexity (Ca·Ce = 15).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.


Graphify review — findings

Adds a content-addressed blob store to FilesystemCache: downloaded weights are stored once at cache/blobs/<sha256[:2]>/<sha256> and exposed to callers as a hard link at cache/models/<name>/<tag>.gguf, falling back to a copy where hard links are unavailable. Makes ModelManager.install() skip the download entirely when the catalog publishes a variant's sha256 and those bytes are already stored, and reworks evict()/clean() to reference-count blobs so weights are reclaimed only once the last referencing entry is gone and orphaned blobs are pruned. Introduces ContentStorePort as an optional companion to CachePort (manifest-only caches stay valid), and adds a pull_request_target labeler workflow that additively tags PRs by touched area from .github/labeler.yml.

Worth a look

  • store_blob can discard the download when a reused blob is deleted after the existence checksrc/modeldock/adapters/cache/filesystem.py:223 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • clean() prunes orphan blobs from the surviving-entries snapshot but does not respect grace period for blobs recorded via record but not touchedsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Manifest read-modify-write is not atomic across evict/clean/recordsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • clean() can delete a blob for an in-flight install past grace windowsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • clean() return value now includes pruned blob digests mixed with entry keyssrc/modeldock/adapters/cache/filesystem.py:132 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 487 functions depend on the 400 functions this change touches.

Health — this change adds coupling hotspots:

  • new: ModelManager — 34 callers, 25 callees
  • new: _manager() — 21 callers, 3 callees
  • new: _manager() — 10 callers, 3 callees
  • new: _manager() — 5 callers, 3 callees

Verification — 487 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 487 function(s) in the blast radius were not formally verified this run

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

)


def _manager(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_manager()

high coupling complexity (Ca·Ce = 15).

Grounded coupling-delta finding (deterministic), not an LLM guess.

lock = _lock(tmp_path)
with pytest.raises(RuntimeError), lock:
raise RuntimeError("boom")
assert not lock.held

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix: Restructure the test so CodeQL can see that execution continues after the exception is caught. The combined form (with pytest.raises(RuntimeError), lock:) confuses the analyzer — it does not model pytest.raises as an exception-catching context manager, so it treats everything after the raise as dead code.

Separate the two context managers:

def test_releases_on_exception(tmp_path: Path) -> None:
    lock = _lock(tmp_path)
    with pytest.raises(RuntimeError):
        with lock:
            raise RuntimeError('boom')
    assert not lock.held  # now visibly reachable
    with lock:
        assert lock.held

Behaviour is identical: CacheLock.exit still receives the exception (verifying the lock is released on error), and pytest.raises catches it. The assert not lock.held line is now clearly reachable to both humans and CodeQL.

Comment thread src/modeldock/ports/cache.py Fixed

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.


Graphify review — findings

Adds a content-addressed blob store to FilesystemCache: downloaded weights land at cache/blobs/<sha256[:2]>/<sha256> and are exposed at cache/models/<name>/<tag>.gguf via a hard link (falling back to a copy where links aren't available), so byte-identical weights cost disk once and are reference-counted across manifest entries. Exposes this behind a new optional ContentStorePort separate from CachePort, and makes ModelManager.install() skip the download entirely when the catalog publishes a variant's sha256 whose bytes are already stored, while evict() reclaims a blob only after its last referencing entry is gone and clean() prunes unreferenced blobs. Also adds a pull_request_target PR labeler workflow that tags PRs by touched area (cli, core, adapters, docs, tests, ci), creating the labels idempotently and only ever adding them.

Worth a look

  • transaction deadlocks when wrapping cache mutationssrc/modeldock/adapters/cache/filesystem.py:54 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Concurrent HTTP installs write the same destination outside the cache transactionsrc/modeldock/core/manager.py:393 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Privileged pull_request_target workflow uses unpinned actions.github/workflows/labeler.yml:43 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Concurrent hard-link creation can fall through to copying onto the same filesrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Freshly stored blobs can be pruned immediately if source mtime is oldsrc/modeldock/adapters/cache/filesystem.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 534 functions depend on the 450 functions this change touches.

Health — this change adds coupling hotspots:

  • new: ModelManager — 34 callers, 25 callees
  • new: FilesystemCache — 48 callers, 3 callees
  • new: _manager() — 21 callers, 3 callees
  • new: _manager() — 10 callers, 3 callees
  • new: _manager() — 5 callers, 4 callees

Verification — 534 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 534 function(s) in the blast radius were not formally verified this run

· 2 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

Comment thread src/modeldock/adapters/cache/filesystem.py
Comment thread tests/unit/test_content_store.py
@albin-george-kurian

Copy link
Copy Markdown
Contributor Author

@himanshu231204, Can you guide me what to do in here to unblock it?

@himanshu231204

Copy link
Copy Markdown
Member

PR Review — Content-Addressed Cache + PR Auto-Labeling

Hi @albin-george-kurian, great contribution! The design is solid and the test coverage is impressive. Here's a detailed walkthrough to help you land this cleanly.


What's done really well

Architecture: ContentStorePort as a separate, optional Protocol (rather than bolting blob methods onto CachePort) is exactly the right call. Manifest-only caches (Ollama) stay valid implementations and callers feature-detect blob support with isinstance. Clean separation of concerns.

Reference counting: The interlock between evict()_gc_blob() and clean()_prune_orphan_blobs() is correct. A blob is only deleted once every manifest entry referencing it is gone. The 5-minute orphan grace period is a nice touch to cover the window between store_blob and record_artifact.

Locking: CacheLock is re-entrant within a thread (so nested locked methods don't deadlock) and gracefully degrades instead of blocking an install forever. That's the right tradeoff for a UX-facing tool.

Security: _remove_artifact validates that the path lives inside the cache dir before unlinking it. Good — manifest files are user-editable.

pull_request_target usage: Your justification is correct. No PR code is checked out, so the elevated token is safe. Worth noting this in a code comment (you already have it in the workflow header — that's sufficient).


Issues / suggestions

1. _install_via_httpdest is silently consumed then recreated (confusing flow)

# manager.py ~line 400
self._http_downloader.download(spec, dest, self._progress)
with store.transaction():
    blob, digest = store.store_blob(dest, expected or None)  # <-- dest is moved/deleted here
    store.link_into(blob, dest)                              # <-- dest is re-created as a hard link

store_blob moves dest into the blob store (consuming it). Then link_into re-creates dest as a hard link to the blob. This is correct, but it looks like a use-after-delete to anyone reading manager.py in isolation. A one-line comment like:

blob, digest = store.store_blob(dest, expected or None)
# dest is now consumed (moved to blob store); link_into re-creates it as a hard link.
store.link_into(blob, dest)

...would save future readers from having to trace into FilesystemCache.

2. _prune_orphan_blobs re-reads the manifest inside the lock — make sure this is intentional

# filesystem.py
referenced |= self._referenced_digests(self._read_manifest().get("entries", {}))

This second manifest read is inside _lock (because clean() holds it). Since CacheLock is re-entrant, this won't deadlock. But the comment says "an entry written by a process that did not take the lock still protects its weights". That's only true if the other process already wrote the manifest. If process B is between store_blob and record_artifact, the grace period is what actually saves it — the second manifest read adds safety only for processes that have already written. The comment slightly overstates the protection. Consider clarifying:

"Unions with a fresh manifest read in case another process wrote an entry after we started; the orphan grace period protects entries still in-flight."

3. blob.name check in _prune_orphan_blobs silently skips shard directories

for blob in sorted(self._blobs_dir.rglob("*")):
    if not blob.is_file() or blob.name in referenced:

referenced is a set of 64-char hex digests. blob.name for a shard directory would be something like "ab" — but rglob("*") also yields directories, and the not blob.is_file() guard catches them. So it's correct. Just worth a comment because the guard doing double duty (not is_file skips dirs, blob.name in referenced skips live blobs) isn't immediately obvious. Maybe split it:

for blob in sorted(self._blobs_dir.rglob("*")):
    if not blob.is_file():
        continue  # shard dirs and other non-files
    if blob.name in referenced:
        continue

4. _normalize_digest error message could be clearer

raise CacheError(f"Not a valid SHA-256 digest: {sha256!r}")

You validate both length and character set but the error doesn't distinguish. This makes debugging slightly harder when someone passes a truncated digest vs. a non-hex string. Not a blocker, just a polish suggestion:

if len(digest) != 64:
    raise CacheError(f"Expected 64-hex-char SHA-256, got {len(digest)} chars: {sha256!r}")
if not set(digest) <= _HEX_DIGITS:
    raise CacheError(f"SHA-256 contains non-hex characters: {sha256!r}")

5. Labeler: pyproject.toml goes to area: ci — intended?

pyproject.toml holds project metadata, dependencies and tool config. PRs that only bump a dependency (not a CI tool) will get area: ci. This might be confusing. Consider whether area: core is a better home, or document the choice in a comment in labeler.yml.

6. Minor: lock.py comment says "never worse than the unsynchronized behaviour it replaces"

This is true for the lock timeout path (caller proceeds without the lock). But if two processes both fall back without the lock, a concurrent clean + record_artifact could still lose the entry — exactly the race you're fixing. The comment is technically accurate ("not worse than before") but could be read as "always safe". Consider: "falls back to unsynchronized access rather than blocking the user's install."


Test coverage looks solid

  • 29 content store tests covering dedup, mismatch rejection, hard-link fallback, reference counting, and orphan pruning — great.
  • Lock re-entrancy and cross-thread exclusion tests are exactly what you need.
  • test_labeler_config.py validating globs against real paths is a clever way to keep the labeler honest as the tree evolves.
  • test_workflow_permissions.py checking exact permission blocks and pull_request_target — good for catching accidental scope creep.

Checklist before merge

  • Add a comment to _install_via_http explaining that dest is consumed by store_blob and recreated by link_into
  • Clarify the _prune_orphan_blobs comment about what the second manifest read actually protects
  • (Optional) Split the blob.is_file() or blob.name in referenced guard for readability
  • Decide on pyproject.toml label routing and document if intentional

The feature itself is well-implemented. The items above are mostly documentation/clarity improvements — none of them are logic bugs. Great work on a non-trivial PR!

@himanshu231204

Copy link
Copy Markdown
Member

Resolving the GitHub Advanced Security (CodeQL) findings

Hi @albin-george-kurian — here's what CodeQL is flagging and exactly how to fix each one so those alerts go green.


1. "Statement has no effect" — src/modeldock/ports/cache.py (lines 84, 88, 97, 101, 112)

What CodeQL sees: The ... (Ellipsis literal) in each ContentStorePort method body is being flagged as a no-op statement.

Why it happens: CodeQL doesn't recognize ... as an intentional Protocol stub idiom. It's treating the Ellipsis the same as a standalone expression like 42 or "unused string".

Fix: Replace ... with pass in every Protocol method body. pass is semantically identical but CodeQL understands it as intentional. Change every stub in ContentStorePort:

# Before
def has_blob(self, sha256: str) -> bool:
    """Return True if weights with this digest are already stored."""
    ...

# After
def has_blob(self, sha256: str) -> bool:
    """Return True if weights with this digest are already stored."""
    pass

Apply this to all 6 affected methods: has_blob, blob_path, store_blob, link_into, record_artifact, and transaction.

Note: Also check the existing CachePort stubs in the same file — if those already use ..., standardize them to pass too, or you'll see the same alert on the next PR that touches that file.


2. "Unreachable code" — tests/unit/test_cache_lock.py line 47

What CodeQL sees: The assert not lock.held after the with pytest.raises(RuntimeError), lock: block is flagged as unreachable.

Why it happens: CodeQL doesn't model pytest.raises as an exception-catching context manager. It assumes the RuntimeError propagates past the with block, making everything after it dead code.

Fix: Restructure the test so the exception scope is clearly nested inside a regular Python with:

# Before
def test_releases_on_exception(tmp_path: Path) -> None:
    lock = _lock(tmp_path)
    with pytest.raises(RuntimeError), lock:
        raise RuntimeError("boom")
    assert not lock.held
    # Still usable afterwards.
    with lock:
        assert lock.held

# After
def test_releases_on_exception(tmp_path: Path) -> None:
    lock = _lock(tmp_path)
    with pytest.raises(RuntimeError):
        with lock:
            raise RuntimeError("boom")
    assert not lock.held  # now clearly reachable — pytest.raises swallowed the exception
    with lock:
        assert lock.held

The test behaviour is identical — __exit__ on CacheLock is still called with the exception (checking that it releases the lock), and pytest.raises catches it. But now CodeQL can see the control flow.


3. "Unnecessary lambda" — tests/unit/test_labeler_config.py

What CodeQL sees: A lambda wrapping a callable that could be used directly (e.g., lambda x: str(x) instead of just str).

Likely location: CodeQL reported this at file-level (no specific line), which usually means it was introduced in an earlier commit and may already be fixed in your latest push. Scan the file for any pattern like lambda x: some_func(x) — if you find one, replace it with some_func directly.

If you don't see any lambdas in the current file, this alert may be stale from a prior commit. You can verify by checking the code scanning alert — if it still points to a line in the current HEAD, fix it; if it shows as resolved, you're good.


Summary of changes needed

File Change
src/modeldock/ports/cache.py Replace ... with pass in all 6 ContentStorePort stub methods
tests/unit/test_cache_lock.py Nest with lock: inside with pytest.raises(RuntimeError): instead of using the combined form
tests/unit/test_labeler_config.py Check for any lambda x: f(x) patterns; replace with f directly (may already be resolved)

These are all non-functional changes — the behaviour of the code and tests stays exactly the same. Once pushed, the CodeQL alerts should close automatically on re-scan.

@albin-george-kurian

Copy link
Copy Markdown
Contributor Author

Thank you, @himanshu231204, for answering my query. I will make the changes accordingly.

@albin-george-kurian

Copy link
Copy Markdown
Contributor Author

@himanshu231204 , is this rdy?

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.

CI: label PRs by changed paths Cache: add content-addressed storage layout

3 participants