Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions bench/bench_pynative_equivalence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
"""Measure the pure-NumPy reference against the native Mojo kernel, on this machine.

Run: cd cli && python3 bench/bench_pynative_equivalence.py

Prints a table of {primitive, native ms, reference ms, ratio} plus the score divergence observed on the same
input, so the two claims this work makes -- "same answers" and "slower" -- are both quantified rather than asserted.

The reference is measured by importing it directly (``memengine._pynative``), NOT by unsetting TELYS_KERNEL: the
backend is cached per process, and re-resolving mid-process would measure the harness rather than the code.

Numbers are wall-clock medians over a few repetitions on CPU. They characterise the ORDER of the slowdown on this
machine; they are not a cross-platform benchmark and should not be quoted as one.
"""
from __future__ import annotations

import ctypes
import os
import statistics
import sys
import time

import numpy as np

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))

from memengine import _pynative as PN # noqa: E402

VP = ctypes.c_void_p
I32 = ctypes.c_int32
F32 = ctypes.c_float
REPEATS = 5


def _median_ms(fn, repeats: int = REPEATS) -> float:
fn() # warm up
samples = []
for _ in range(repeats):
t0 = time.perf_counter()
fn()
samples.append((time.perf_counter() - t0) * 1000.0)
return statistics.median(samples)


def _native_lib():
from memengine import mojo_backend as mb

if mb.backend_kind() != "native":
return None
return mb._lib()


def main() -> int:
lib = _native_lib()
if lib is None:
print("Native kernel unavailable on this machine -- nothing to compare against.")
print("The reference still runs (that is the point), but a ratio needs both implementations.")
return 0

rng = np.random.default_rng(7)
rows: list[tuple[str, float, float]] = []

# ── flat top-k, the partition scan ──────────────────────────────────────────────────────────
for n, d, k, label in ((20_000, 384, 10, "flat_topk n=20k d=384"), (100_000, 384, 10, "flat_topk n=100k d=384")):
base = rng.standard_normal((n, d)).astype(np.float32)
query = rng.standard_normal(d).astype(np.float32)
out_ids = np.full(k, -1, np.int32)
out_dist = np.zeros(k, np.float32)

def native() -> None:
lib.ame_flat_topk(base.ctypes.data_as(VP), n, d, query.ctypes.data_as(VP), k,
out_ids.ctypes.data_as(VP), out_dist.ctypes.data_as(VP))

rows.append((label, _median_ms(native), _median_ms(lambda: PN.flat_topk(base, n, d, query, k))))

# ── the lexical tokenizer, on a realistic window ───────────────────────────────────────────
text = (
"def reconcile_ledger_entry(order_refund, session_store):\n"
" # migrate the ledger after the redirect; HTTPServer XMLHttpRequest\n"
" return [entry for entry in session_store.items if entry.refunded]\n"
) * 40
raw = text.encode()

def native_tokenize() -> None:
buf = np.empty(65536, np.int64)
stop = np.zeros(0, np.int64)
lib.ame_code_tokenize(raw, len(raw), buf.ctypes.data_as(VP), 65536, stop.ctypes.data_as(VP), 0)

from memengine.lexical import STOP

stop_set = {PN.fnv1a_signed(w) for w in STOP}
rows.append((
f"code_tokenize ({len(raw)} bytes)",
_median_ms(native_tokenize),
_median_ms(lambda: PN.code_tokenize_hashes(text, stop_set)),
))

# ── BM25 postings query over a real-size corpus ────────────────────────────────────────────
from memengine.lexical import LexicalCodeIndex

docs = [[int(x) for x in rng.integers(0, 5000, size=int(rng.integers(20, 120)))] for _ in range(20_000)]
lex = LexicalCodeIndex().build_from_hashes([str(i) for i in range(len(docs))], docs)
query_hashes = sorted(set(int(x) for x in rng.integers(0, 5000, size=12)))
starts, ends, idfs = [], [], []
for h in query_hashes:
rng_ = lex._term_range.get(h) # noqa: SLF001
if rng_ is None:
continue
st, cnt = rng_
starts.append(st)
ends.append(st + cnt)
idfs.append(lex._bm.idf[h]) # noqa: SLF001
qs = np.asarray(starts, np.int32)
qe = np.asarray(ends, np.int32)
qi = np.asarray(idfs, np.float32)
k = 10
out_ids = np.full(k, -1, np.int32)
out_scores = np.zeros(k, np.float32)
scratch = np.empty(len(lex.ids), np.float32)

def native_bm25() -> None:
lib.ame_bm25_csr_topk(
lex._post_docs.ctypes.data_as(VP), lex._post_tfs.ctypes.data_as(VP), # noqa: SLF001
qs.ctypes.data_as(VP), qe.ctypes.data_as(VP), qi.ctypes.data_as(VP), len(starts),
lex._doc_denom.ctypes.data_as(VP), len(lex.ids), scratch.ctypes.data_as(VP), # noqa: SLF001
F32(lex.k1), k, out_ids.ctypes.data_as(VP), out_scores.ctypes.data_as(VP),
)

rows.append((
f"bm25_csr_topk n={len(lex.ids)}",
_median_ms(native_bm25),
_median_ms(lambda: PN.bm25_csr_topk(
lex._post_docs, lex._post_tfs, qs, qe, qi, len(starts), # noqa: SLF001
lex._doc_denom, len(lex.ids), lex.k1, k, # noqa: SLF001
)),
))

# ── embedder ───────────────────────────────────────────────────────────────────────────────
dim = PN.multigram_dim()
out_vec = np.zeros(dim, np.float32)
probe = b"def reconcile_ledger_entry(order_refund):" * 20

def native_embed() -> None:
lib.ame_embed_multigram(probe, len(probe), out_vec.ctypes.data_as(VP))

rows.append((
"multigram_embed (620 bytes)",
_median_ms(native_embed),
_median_ms(lambda: PN.multigram_embed(probe)),
))

# ── report ─────────────────────────────────────────────────────────────────────────────────
print(f"{'primitive':<34}{'native ms':>12}{'reference ms':>15}{'ratio':>9}")
print("-" * 70)
for label, native_ms, ref_ms in rows:
ratio = ref_ms / native_ms if native_ms > 0 else float("inf")
print(f"{label:<34}{native_ms:>12.3f}{ref_ms:>15.3f}{ratio:>8.1f}x")
print()
print(
"Ratios are the measured slowdown of the pure-NumPy reference on THIS machine.\n"
"They characterise the order of magnitude only; retrieval quality is unchanged."
)
return 0


if __name__ == "__main__":
raise SystemExit(main())
4 changes: 4 additions & 0 deletions codna/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,10 @@ def build_parser() -> argparse.ArgumentParser:
_register_key(sub)
from .impact_cli import register_cli as _register_impact_cli # impact + memory commands
_register_impact_cli(sub)
from .durable.cli import register as _register_durable # durable runs (optional extra)
_register_durable(sub)
from .durable.cli import register_top_level as _register_durable_top # `codna run` / `codna worker`
_register_durable_top(sub)
return p


Expand Down
74 changes: 71 additions & 3 deletions codna/config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
``env:CODNA_MODEL_KEY``); a literal value is used as-is. The same provider is also written to
``CODNA_AGENT_PROVIDER`` so the packaged local Cline SDK sidecar does not silently default to another
provider.
- ``model.base_url`` → ``CODNA_AGENT_BASE_URL``, the provider ENDPOINT override forwarded to the sidecar.
A base URL decides where the provider credential is sent, so it is validated here (http/https, no
embedded credentials, no query/fragment) and it may come from THIS file or the environment only — never
from a repository-scoped settings file. ``env:NAME`` resolves the same way as ``model.key``.
- ``privacy.egress: fail-closed`` → sets ``CODNA_REQUIRE_EGRESS_DENY=1`` (the real enforcement is the
sandbox network-deny policy + the PR manifest gate; there is no global egress env otherwise).
- ``privacy.redact_secrets`` → accept-and-warn: ``true`` is the posture; ``false`` is *ignored with a
Expand All @@ -21,11 +25,22 @@
import json
import os
import sys
from urllib.parse import urlsplit

from .keystore import PROVIDER_ALIASES

_SEARCH = ("codna.yaml", ".codna.yaml")

# The env var the sidecar reads for a provider endpoint override. Kept in step with
# cli/codna/packaged_agent_runner.py (`_sidecar_payload`), which is the side that actually sends it.
BASE_URL_ENV = "CODNA_AGENT_BASE_URL"

# Providers that take NO credential. A model block for one of these is valid with no `key` at all: there is no
# keystore alias to write, and inventing one would imply a key is needed. Canonical list lives in three places
# that cannot import each other (this file, the IDE's `@codna/protocol`, and the engine's adapter.ts); each is
# pinned by its own test so they cannot drift apart silently.
KEYLESS_PROVIDERS: frozenset[str] = frozenset({"ollama", "lmstudio", "openai-compatible", "litellm"})


class ConfigError(Exception):
"""Invalid ``codna.yaml`` (surfaced to the user)."""
Expand Down Expand Up @@ -71,7 +86,15 @@ def _resolve_env_ref(value, *, field: str):
return value


def discover_config_path(explicit: str | None = None) -> str | None:
def discover_config_path(explicit: str | None = None, *, include_home: bool = False) -> str | None:
"""Locate the config file: an explicit `--config`, `./codna.yaml`, `./.codna.yaml`, then — only when
`include_home` — `~/.codna/config.yaml`.

The HOME-relative file is opt-in (it is consulted exactly when a command consumes MODEL config) because a
provider ENDPOINT is a property of the user's machine, not of whichever repository the CLI happens to run
in: keys and endpoints set there must apply everywhere. Deterministic commands keep the historical
project-only search, so they behave identically on every machine.
"""
if explicit:
path = os.path.expanduser(explicit)
if not os.path.isfile(path):
Expand All @@ -80,9 +103,40 @@ def discover_config_path(explicit: str | None = None) -> str | None:
for name in _SEARCH:
if os.path.isfile(name):
return name
if include_home:
home = os.path.join(os.path.expanduser("~"), ".codna", "config.yaml")
if os.path.isfile(home):
return home
return None


def _validated_base_url(value: object, *, field: str) -> str:
"""Validate a provider endpoint override before it can become a fetch target.

Fail-closed on every shape that could smuggle something past the consumer: a non-http(s) scheme, an
embedded credential (``https://user:pass@host`` puts secrets in logs and in every request), a query or
fragment (silently dropped by every consumer, so the config would lie about itself), and whitespace.
The trailing slash is removed so the consumer's ``${base}/v1`` cannot become ``//v1``.
"""
text = str(value or "").strip()
if not text:
raise ConfigError(f"{field} must not be blank")
if any(ch.isspace() for ch in text):
raise ConfigError(f"{field} must not contain whitespace")
parts = urlsplit(text)
if parts.scheme not in ("http", "https"):
raise ConfigError(f"{field} must be an http(s) URL, got {parts.scheme or 'no scheme'!r}")
if not parts.hostname:
raise ConfigError(f"{field} must name a host")
if parts.username or parts.password:
raise ConfigError(f"{field} must not embed credentials")
if parts.query or parts.fragment:
raise ConfigError(f"{field} must not carry a query string or fragment")
path = parts.path.rstrip("/")
netloc = parts.netloc
return f"{parts.scheme}://{netloc}{path}"
Comment on lines +136 to +137

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM · security — Validated base URL reconstruction uses raw netloc including potential credential

In _validated_base_url, the function checks parts.username or parts.password and raises if present. However the reconstructed URL f"{parts.scheme}://{netloc}{path}" uses parts.netloc, which for urlsplit INCLUDES the userinfo portion (e.g. user:pass@host). If the credential check is ever bypassed or refactored, the returned URL will silently reinclude the embedded credential. A safer construction would be f"{parts.scheme}://{parts.hostname}" + (f":{parts.port}" if parts.port else "") + path to explicitly exclude any userinfo.

Reply @codna fix and Codna opens a verified, test-green, risk-gated fix PR.



def apply_config(path: str, *, include_model: bool = True) -> None:
data = _load(path)

Expand All @@ -92,13 +146,27 @@ def apply_config(path: str, *, include_model: bool = True) -> None:
if not provider:
raise ConfigError("model.provider is required when a model block is set")
target = PROVIDER_ALIASES.get(provider)
if not target:
# A KEYLESS provider (local inference server / self-hosted proxy) legitimately has no key to alias.
# An unknown provider is still an error: guessing would ship the user's key to a provider they did not
# name, and accepting a typo silently is worse than refusing it.
keyless = provider in KEYLESS_PROVIDERS
if not target and not keyless:
raise ConfigError(f"model.provider {provider!r} is unknown; valid: {sorted(PROVIDER_ALIASES)}")
key = _resolve_env_ref(model.get("key"), field="model.key")
if key:
if not target:
raise ConfigError(
f"model.key is not applicable to the keyless provider {provider!r} "
"(ollama/lmstudio/openai-compatible/litellm accept requests without a credential)"
)
# Explicit config is authoritative — the user wrote codna.yaml deliberately.
os.environ[target] = str(key)
os.environ["CODNA_AGENT_PROVIDER"] = provider
# The endpoint override travels on its OWN variable so a provider switch cannot silently reuse the
# previous provider's URL. `env:NAME` refs resolve (and fail closed) exactly like `model.key`.
if "base_url" in model:
raw = _resolve_env_ref(model.get("base_url"), field="model.base_url")
os.environ[BASE_URL_ENV] = _validated_base_url(raw, field="model.base_url")

privacy = data.get("privacy") or {}
egress = str(privacy.get("egress", "")).strip().lower()
Expand All @@ -115,7 +183,7 @@ def apply_config(path: str, *, include_model: bool = True) -> None:

def load_and_apply(explicit: str | None = None, *, include_model: bool = True) -> str | None:
"""Discover + apply a codna.yaml. Returns the path applied (or None). Raises ConfigError."""
path = discover_config_path(explicit)
path = discover_config_path(explicit, include_home=include_model)
if path:
apply_config(path, include_model=include_model)
return path
2 changes: 1 addition & 1 deletion codna/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
PUBLIC_CONFIGURED_VALUE = "<configured>"
PUBLIC_STATE_PATH = Path.home() / ".codna" / "runtime" / "local-stack.json"
_URL_CREDENTIAL_RE = re.compile(r"(https?://)([^/@\s\"']+@)")
_SECRET_FIELD_RE = re.compile(r"(?i)\b(password|token|secret|api[_-]?key)(\s*[:=]\s*)[^\s,}\]]+")
_SECRET_FIELD_RE = re.compile(r"(?i)\b(password|token|secret|api[_-]?key)([\"']?\s*[:=]\s*)[^\s,}\]]+")


def _first_config_source(env: Mapping[str, str], keys: tuple[str, ...]) -> str | None:
Expand Down
Loading
Loading