Skip to content

Commit dd0b077

Browse files
fix: resolve test failures and lint for v0.5.0 release
Updated trust weight tests for 2D scoring (trust * adversarial). Fixed import sorting, type annotations for mypy strict. All 408 tests passing, lint clean, build verified on Python 3.12.
1 parent 6881bb7 commit dd0b077

7 files changed

Lines changed: 452 additions & 50 deletions

File tree

src/qp_vault/cli/main.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -226,28 +226,32 @@ def verify(
226226
vault = _get_vault(path)
227227

228228
if resource_id:
229-
result = vault.verify(resource_id)
230-
if result.passed:
229+
from qp_vault.models import VerificationResult
230+
r = vault.verify(resource_id)
231+
assert isinstance(r, VerificationResult)
232+
if r.passed:
231233
console.print(f"[green]PASS[/green] {resource_id}")
232-
console.print(f" Hash: {result.stored_hash}")
233-
console.print(f" Chunks verified: {result.chunk_count}")
234+
console.print(f" Hash: {r.stored_hash}")
235+
console.print(f" Chunks verified: {r.chunk_count}")
234236
else:
235237
console.print(f"[red]FAIL[/red] {resource_id}")
236-
console.print(f" Stored: {result.stored_hash}")
237-
console.print(f" Computed: {result.computed_hash}")
238-
if result.failed_chunks:
239-
console.print(f" Failed chunks: {len(result.failed_chunks)}")
238+
console.print(f" Stored: {r.stored_hash}")
239+
console.print(f" Computed: {r.computed_hash}")
240+
if r.failed_chunks:
241+
console.print(f" Failed chunks: {len(r.failed_chunks)}")
240242
raise typer.Exit(1)
241243
else:
242-
result = vault.verify()
243-
if result.passed:
244+
from qp_vault.models import VaultVerificationResult
245+
vr = vault.verify()
246+
assert isinstance(vr, VaultVerificationResult)
247+
if vr.passed:
244248
console.print("[green]PASS[/green] Vault integrity verified")
245-
console.print(f" Resources: {result.resource_count}")
246-
console.print(f" Merkle root: {result.merkle_root}")
247-
console.print(f" Duration: {result.duration_ms}ms")
249+
console.print(f" Resources: {vr.resource_count}")
250+
console.print(f" Merkle root: {vr.merkle_root}")
251+
console.print(f" Duration: {vr.duration_ms}ms")
248252
else:
249253
console.print("[red]FAIL[/red] Vault integrity check failed")
250-
console.print(f" Failed: {len(result.failed_resources)} resources")
254+
console.print(f" Failed: {len(vr.failed_resources)} resources")
251255
raise typer.Exit(1)
252256

253257

src/qp_vault/core/search_engine.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,34 @@
1515
from datetime import UTC, datetime
1616
from typing import TYPE_CHECKING
1717

18-
from qp_vault.enums import TrustTier
18+
from qp_vault.enums import AdversarialStatus, ResourceStatus, TrustTier
1919

2020
if TYPE_CHECKING:
2121
from qp_vault.config import VaultConfig
2222
from qp_vault.models import SearchResult
2323

24-
# Default trust weights
24+
# Default trust weights (organizational dimension)
2525
TRUST_WEIGHTS: dict[str, float] = {
2626
TrustTier.CANONICAL.value: 1.5,
2727
TrustTier.WORKING.value: 1.0,
2828
TrustTier.EPHEMERAL.value: 0.7,
2929
TrustTier.ARCHIVED.value: 0.25,
3030
}
3131

32+
# Adversarial verification multipliers (security dimension)
33+
# Effective RAG weight = trust_weight * adversarial_multiplier
34+
ADVERSARIAL_MULTIPLIERS: dict[str, float] = {
35+
AdversarialStatus.VERIFIED.value: 1.0,
36+
AdversarialStatus.UNVERIFIED.value: 0.7,
37+
AdversarialStatus.SUSPICIOUS.value: 0.3,
38+
}
39+
40+
# Statuses excluded from search results by default
41+
EXCLUDED_STATUSES: set[str] = {
42+
ResourceStatus.QUARANTINED.value,
43+
ResourceStatus.DELETED.value,
44+
}
45+
3246
# Default freshness half-life in days
3347
FRESHNESS_HALF_LIFE: dict[str, int] = {
3448
TrustTier.CANONICAL.value: 365,
@@ -44,6 +58,23 @@ def compute_trust_weight(trust_tier: str, config: VaultConfig | None = None) ->
4458
return weights.get(trust_tier, 1.0)
4559

4660

61+
def compute_adversarial_multiplier(adversarial_status: str) -> float:
62+
"""Get adversarial verification multiplier.
63+
64+
Returns the security-dimension weight for a given adversarial status.
65+
Effective RAG weight = trust_weight * adversarial_multiplier.
66+
"""
67+
return ADVERSARIAL_MULTIPLIERS.get(adversarial_status, 0.7)
68+
69+
70+
def is_searchable(status: str) -> bool:
71+
"""Check if a resource with the given status should appear in search results.
72+
73+
QUARANTINED and DELETED resources are excluded by default.
74+
"""
75+
return status not in EXCLUDED_STATUSES
76+
77+
4778
def compute_freshness(
4879
updated_at: datetime | str | None,
4980
trust_tier: str,
@@ -88,18 +119,23 @@ def apply_trust_weighting(
88119
for result in results:
89120
tier = result.trust_tier.value if hasattr(result.trust_tier, "value") else str(result.trust_tier)
90121
tw = compute_trust_weight(tier, config)
122+
123+
# CIS 2D trust: multiply by adversarial verification status
124+
adv_status = getattr(result, "adversarial_status", None)
125+
adv_str = adv_status.value if hasattr(adv_status, "value") else str(adv_status or "unverified")
126+
adv_mult = compute_adversarial_multiplier(adv_str)
127+
91128
# Freshness: we don't have updated_at on SearchResult, use 1.0 for now
92-
# (will be enhanced when we add resource timestamps to search results)
93129
freshness = 1.0
94130

95-
# Composite score
96-
raw = result.relevance # Already computed by storage backend
97-
composite = raw * tw * freshness
131+
# Composite score: raw * organizational_trust * adversarial_verification * freshness
132+
raw = result.relevance
133+
composite = raw * tw * adv_mult * freshness
98134

99135
weighted.append(
100136
result.model_copy(
101137
update={
102-
"trust_weight": tw,
138+
"trust_weight": tw * adv_mult,
103139
"freshness": freshness,
104140
"relevance": composite,
105141
}

src/qp_vault/integrations/fastapi_routes.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def _handle_error(e: Exception) -> HTTPException:
117117
return HTTPException(status_code=500, detail="Internal server error")
118118

119119
@router.post("/resources")
120-
async def add_resource(req: AddResourceRequest) -> dict:
120+
async def add_resource(req: AddResourceRequest) -> dict[str, Any]:
121121
resource = await vault.add(
122122
req.content,
123123
name=req.name,
@@ -139,7 +139,7 @@ async def list_resources(
139139
status: str | None = None,
140140
limit: int = 50,
141141
offset: int = 0,
142-
) -> dict:
142+
) -> dict[str, Any]:
143143
resources = await vault.list(
144144
trust=trust,
145145
layer=layer,
@@ -151,15 +151,15 @@ async def list_resources(
151151
return {"data": [r.model_dump() for r in resources], "meta": {"count": len(resources)}}
152152

153153
@router.get("/resources/{resource_id}")
154-
async def get_resource(resource_id: str) -> dict:
154+
async def get_resource(resource_id: str) -> dict[str, Any]:
155155
try:
156156
resource = await vault.get(resource_id)
157157
except Exception as e:
158158
raise _handle_error(e) from e
159159
return {"data": resource.model_dump(), "meta": {}}
160160

161161
@router.put("/resources/{resource_id}")
162-
async def update_resource(resource_id: str, req: UpdateResourceRequest) -> dict:
162+
async def update_resource(resource_id: str, req: UpdateResourceRequest) -> dict[str, Any]:
163163
try:
164164
resource = await vault.update(
165165
resource_id,
@@ -174,49 +174,49 @@ async def update_resource(resource_id: str, req: UpdateResourceRequest) -> dict:
174174
return {"data": resource.model_dump(), "meta": {}}
175175

176176
@router.delete("/resources/{resource_id}")
177-
async def delete_resource(resource_id: str, hard: bool = False) -> dict:
177+
async def delete_resource(resource_id: str, hard: bool = False) -> dict[str, Any]:
178178
try:
179179
await vault.delete(resource_id, hard=hard)
180180
except Exception as e:
181181
raise _handle_error(e) from e
182182
return {"data": {"deleted": True}, "meta": {}}
183183

184184
@router.post("/resources/{resource_id}/transition")
185-
async def transition_resource(resource_id: str, req: TransitionRequest) -> dict:
185+
async def transition_resource(resource_id: str, req: TransitionRequest) -> dict[str, Any]:
186186
try:
187187
resource = await vault.transition(resource_id, req.target, reason=req.reason)
188188
except Exception as e:
189189
raise _handle_error(e) from e
190190
return {"data": resource.model_dump(), "meta": {}}
191191

192192
@router.post("/resources/{resource_id}/supersede")
193-
async def supersede_resource(resource_id: str, req: SupersedeRequest) -> dict:
193+
async def supersede_resource(resource_id: str, req: SupersedeRequest) -> dict[str, Any]:
194194
try:
195195
old, new = await vault.supersede(resource_id, req.new_id)
196196
except Exception as e:
197197
raise _handle_error(e) from e
198198
return {"data": {"old": old.model_dump(), "new": new.model_dump()}, "meta": {}}
199199

200200
@router.get("/resources/{resource_id}/verify")
201-
async def verify_resource(resource_id: str) -> dict:
201+
async def verify_resource(resource_id: str) -> dict[str, Any]:
202202
result = await vault.verify(resource_id)
203203
return {"data": result.model_dump(), "meta": {}}
204204

205205
@router.get("/resources/{resource_id}/proof")
206-
async def export_proof(resource_id: str) -> dict:
206+
async def export_proof(resource_id: str) -> dict[str, Any]:
207207
try:
208208
proof = await vault.export_proof(resource_id)
209209
except Exception as e:
210210
raise _handle_error(e) from e
211211
return {"data": proof.model_dump(), "meta": {}}
212212

213213
@router.get("/resources/{resource_id}/chain")
214-
async def get_chain(resource_id: str) -> dict:
214+
async def get_chain(resource_id: str) -> dict[str, Any]:
215215
chain = await vault.chain(resource_id)
216216
return {"data": [r.model_dump() for r in chain], "meta": {"length": len(chain)}}
217217

218218
@router.post("/search")
219-
async def search(req: SearchRequest) -> dict:
219+
async def search(req: SearchRequest) -> dict[str, Any]:
220220
as_of = date.fromisoformat(req.as_of) if req.as_of else None
221221
results = await vault.search(
222222
req.query,
@@ -233,22 +233,22 @@ async def search(req: SearchRequest) -> dict:
233233
}
234234

235235
@router.get("/verify")
236-
async def verify_all() -> dict:
236+
async def verify_all() -> dict[str, Any]:
237237
result = await vault.verify()
238238
return {"data": result.model_dump(), "meta": {}}
239239

240240
@router.get("/health")
241-
async def health() -> dict:
241+
async def health() -> dict[str, Any]:
242242
score = await vault.health()
243243
return {"data": score.model_dump(), "meta": {}}
244244

245245
@router.get("/status")
246-
async def status() -> dict:
246+
async def status() -> dict[str, Any]:
247247
s = await vault.status()
248248
return {"data": s, "meta": {}}
249249

250250
@router.get("/expiring")
251-
async def expiring(days: int = 90) -> dict:
251+
async def expiring(days: int = 90) -> dict[str, Any]:
252252
resources = await vault.expiring(days=days)
253253
return {"data": [r.model_dump() for r in resources], "meta": {"days": days}}
254254

src/qp_vault/vault.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
)
2525
from qp_vault.exceptions import VaultError
2626
from qp_vault.models import (
27+
HealthScore,
28+
MerkleProof,
2729
Resource,
2830
SearchResult,
2931
VaultVerificationResult,
@@ -43,7 +45,6 @@
4345
from datetime import date
4446

4547
from qp_vault.core.layer_manager import LayerView
46-
from qp_vault.models import HealthScore, MerkleProof
4748

4849
# --- Input Sanitization ---
4950

@@ -763,29 +764,34 @@ def chain(self, resource_id: str) -> list[Resource]:
763764
"""Get supersession chain."""
764765
return _run_async(self._async.chain(resource_id))
765766

766-
def export_proof(self, resource_id: str) -> Any:
767+
def export_proof(self, resource_id: str) -> MerkleProof:
767768
"""Export Merkle proof for auditors."""
768-
return _run_async(self._async.export_proof(resource_id))
769+
result: MerkleProof = _run_async(self._async.export_proof(resource_id))
770+
return result
769771

770772
def search(self, query: str, **kwargs: Any) -> list[SearchResult]:
771773
"""Trust-weighted hybrid search."""
772-
return _run_async(self._async.search(query, **kwargs))
774+
result: list[SearchResult] = _run_async(self._async.search(query, **kwargs))
775+
return result
773776

774777
def verify(self, resource_id: str | None = None) -> VerificationResult | VaultVerificationResult:
775778
"""Verify integrity."""
776-
return _run_async(self._async.verify(resource_id))
779+
result: VerificationResult | VaultVerificationResult = _run_async(self._async.verify(resource_id))
780+
return result
777781

778-
def layer(self, name: MemoryLayer | str) -> Any:
782+
def layer(self, name: MemoryLayer | str) -> LayerView: # type: ignore[return-value]
779783
"""Get a scoped view of a memory layer."""
780-
return self._async.layer(name)
784+
return self._async.layer(name) # type: ignore[return-value]
781785

782-
def health(self) -> Any:
786+
def health(self) -> HealthScore:
783787
"""Compute vault health score."""
784-
return _run_async(self._async.health())
788+
result: HealthScore = _run_async(self._async.health())
789+
return result
785790

786791
def status(self) -> dict[str, Any]:
787792
"""Get vault status."""
788-
return _run_async(self._async.status())
793+
result: dict[str, Any] = _run_async(self._async.status())
794+
return result
789795

790796
def register_embedder(self, embedder: EmbeddingProvider) -> None:
791797
"""Register a custom embedding provider."""

0 commit comments

Comments
 (0)