Skip to content
Merged
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
26 changes: 11 additions & 15 deletions .dagger/src/edge_proc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
"ghcr.io/astral-sh/uv:0.11.32@sha256:"
"df4cae8f3a96d175e2e5f992e597550000edbe78fdc2594d5cd8de1a217f504c"
)
#: The repository as GitHub reports it today; the default keeps existing callers exact.
DEFAULT_REPOSITORY: Final = "hseshadr/edge-proc"
#: The only identities a run may claim: this repository today, and after the org transfer.
ALLOWED_REPOSITORIES: Final = ("hseshadr/edge-proc", "gainratio/edge-proc")
#: The only identities a run may claim: the canonical gainratio owner first, then the
#: pre-transfer identity until the org move finishes. There is deliberately no default:
#: every gate must be handed the run's own `github.repository`.
ALLOWED_REPOSITORIES: Final = ("gainratio/edge-proc", "hseshadr/edge-proc")
PROJECT_NAME: Final = "edge-proc"
CENTRAL_MODULE_SHA: Final = "a88866232e679b6353d2b75bceb01969be739f67"
SOURCE_EXCLUDES: Final = [
Expand Down Expand Up @@ -99,21 +99,19 @@ def quality(self) -> dagger.Container:
return self._quality(self.source)

@function
async def security(self, commit_sha: str, repository: str = DEFAULT_REPOSITORY) -> str:
async def security(self, commit_sha: str, repository: str) -> str:
"""Run the shared exact-source, workflow, and history guard."""
await self._verified_source(self.source, _allowed_repository(repository), commit_sha)
return "EdgeProc shared Dagger security gate passed"

@function
def dependency_audit(
self, commit_sha: str, repository: str = DEFAULT_REPOSITORY
) -> dagger.Container:
def dependency_audit(self, commit_sha: str, repository: str) -> dagger.Container:
"""Audit the locked graph through the shared Python-package Lego."""
return self._dependency_audit(self.source, _allowed_repository(repository), commit_sha)

@function
@check
async def ci(self, commit_sha: str, repository: str = DEFAULT_REPOSITORY) -> str:
async def ci(self, commit_sha: str, repository: str) -> str:
"""Run the canonical exact-source gate sequentially."""
await self._run_ci(self.source, commit_sha, _allowed_repository(repository))
return "EdgeProc canonical Dagger gate passed"
Expand All @@ -122,7 +120,7 @@ async def ci(self, commit_sha: str, repository: str = DEFAULT_REPOSITORY) -> str
@function(cache="never") # type: ignore[call-overload,untyped-decorator] # SDK stub gap
async def release_candidate(
self, tag: str, commit_sha: str, github_token: dagger.Secret,
repository: str = DEFAULT_REPOSITORY,
repository: str,
) -> dagger.Directory:
"""Create and reverify one exact attempt-bound Foundation envelope."""
repository = _allowed_repository(repository)
Expand All @@ -136,7 +134,7 @@ async def release_candidate(

@function(cache="never") # type: ignore[call-overload,untyped-decorator] # SDK stub gap
async def repin_dependabot(
self, github_token: dagger.Secret, pr_number: int, repository: str = DEFAULT_REPOSITORY
self, github_token: dagger.Secret, pr_number: int, repository: str
) -> str:
"""Copy uv.lock tool versions into the literals of one Dependabot uv PR."""
repository = _allowed_repository(repository)
Expand All @@ -155,7 +153,7 @@ async def verify_candidate(
commit_sha: str,
workflow_run_id: str,
run_attempt: int,
repository: str = DEFAULT_REPOSITORY,
repository: str,
) -> dagger.Directory:
"""Revalidate a closed candidate without source or credentials."""
lineage = _Lineage(
Expand All @@ -165,9 +163,7 @@ async def verify_candidate(
await verified.tag()
return verified.envelope()

async def _run_ci(
self, source: dagger.Directory, commit_sha: str, repository: str = DEFAULT_REPOSITORY
) -> None:
async def _run_ci(self, source: dagger.Directory, commit_sha: str, repository: str) -> None:
bound = await self._verified_source(source, repository, commit_sha)
await self._product_gate(bound).sync()
await self._dependency_audit(bound, repository, commit_sha).sync()
Expand Down
39 changes: 27 additions & 12 deletions .dagger/tests/test_public_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

ROOT = Path(__file__).resolve().parents[2]
CENTRAL_SHA = "a88866232e679b6353d2b75bceb01969be739f67"
REPOSITORY = "hseshadr/edge-proc"
REPOSITORY = "gainratio/edge-proc"
PROJECT_NAME = "edge-proc"
COMMIT_SHA = "a" * 40
MAX_FUNCTION_LINES = 15
Expand Down Expand Up @@ -260,7 +260,7 @@ def test_should_bind_guard_then_run_product_and_closed_audit_on_one_source(
edge = _edge(source, events)

# When
asyncio.run(edge._run_ci(source, COMMIT_SHA))
asyncio.run(edge._run_ci(source, COMMIT_SHA, REPOSITORY))

# Then
assert events == ["source", "guard", "product", "audit"]
Expand All @@ -280,7 +280,7 @@ def test_should_query_osv_for_non_pypi_locked_versions(
edge = _edge(cast(dagger.Directory, object()), [])

# When
edge.dependency_audit(COMMIT_SHA)
edge.dependency_audit(COMMIT_SHA, REPOSITORY)

# Then
assert package.audit_commands == [list(dagger_module.OSV_AUDIT_COMMAND)]
Expand All @@ -297,7 +297,7 @@ def test_should_stop_before_product_or_audit_when_shared_guard_rejects(

# When / Then
with pytest.raises(SharedGuardRejectedError, match="shared guard rejected source"):
asyncio.run(edge._run_ci(source, COMMIT_SHA))
asyncio.run(edge._run_ci(source, COMMIT_SHA, REPOSITORY))
assert events == ["source"]
assert edge.product_source is None
assert package.audit_call is None
Expand All @@ -313,7 +313,7 @@ def test_should_create_and_reverify_one_attempt_bound_closed_candidate(
edge = _edge(source, events)

# When
result = asyncio.run(edge.release_candidate("v0.4.1", COMMIT_SHA, token))
result = asyncio.run(edge.release_candidate("v0.4.1", COMMIT_SHA, token, REPOSITORY))

# Then
identity = (REPOSITORY, COMMIT_SHA, PROJECT_NAME, CENTRAL_SHA, "6100", 2)
Expand Down Expand Up @@ -347,7 +347,9 @@ def test_should_reject_manual_tag_mismatch_before_candidate_handoff(

# When / Then
with pytest.raises(ValueError, match="manual tag differs"):
asyncio.run(edge.release_candidate("v0.4.1", COMMIT_SHA, cast(dagger.Secret, object())))
asyncio.run(
edge.release_candidate("v0.4.1", COMMIT_SHA, cast(dagger.Secret, object()), REPOSITORY)
)
assert package.verify_call is None


Expand All @@ -360,7 +362,7 @@ def test_should_verify_candidate_without_source_or_credentials(
edge = _edge(cast(dagger.Directory, object()), events)

# When
result = asyncio.run(edge.verify_candidate(envelope, COMMIT_SHA, "6100", 2))
result = asyncio.run(edge.verify_candidate(envelope, COMMIT_SHA, "6100", 2, REPOSITORY))

# Then
identity = (REPOSITORY, COMMIT_SHA, PROJECT_NAME, CENTRAL_SHA, "6100", 2)
Expand Down Expand Up @@ -399,8 +401,8 @@ def test_should_keep_every_adapter_function_within_the_python_quality_contract()
assert {name: span for name, span in spans.items() if span > MAX_FUNCTION_LINES} == {}


#: The repository as GitHub reports it today, and after the planned transfer to the org.
ALLOWED = ("hseshadr/edge-proc", "gainratio/edge-proc")
#: The canonical gainratio identity, then the pre-transfer one kept until the org move finishes.
ALLOWED = ("gainratio/edge-proc", "hseshadr/edge-proc")

#: A fork, a sibling repository, a look-alike name, a look-alike owner, and nothing.
REFUSED = (
Expand All @@ -412,12 +414,25 @@ def test_should_keep_every_adapter_function_within_the_python_quality_contract()
)


def test_should_default_to_the_repository_identity_used_today() -> None:
@pytest.mark.parametrize(
"gate",
[
"ci",
"security",
"dependency_audit",
"release_candidate",
"repin_dependabot",
"verify_candidate",
"_run_ci",
],
)
def test_should_require_the_runs_repository_with_no_stale_default_owner(gate: str) -> None:
# Given / When
parameter = inspect.signature(EdgeProc.ci).parameters["repository"]
parameter = inspect.signature(getattr(EdgeProc, gate)).parameters["repository"]

# Then
assert parameter.default == "hseshadr/edge-proc"
assert parameter.default is inspect.Parameter.empty
assert not hasattr(dagger_module, "DEFAULT_REPOSITORY")
assert dagger_module.ALLOWED_REPOSITORIES == ALLOWED


Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ authors:
- family-names: Seshadri
given-names: Harish
email: harish.seshadri@gmail.com
repository-code: "https://github.com/hseshadr/edge-proc"
url: "https://github.com/hseshadr/edge-proc"
repository-code: "https://github.com/gainratio/edge-proc"
url: "https://github.com/gainratio/edge-proc"
license: MIT
version: 0.5.0
keywords:
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ New here? [Getting started for developers](docs/GETTING_STARTED.md) walks you fr
clone to a green local build and your first change, with the time each step takes.

```bash
git clone https://github.com/hseshadr/edge-proc.git
git clone https://github.com/gainratio/edge-proc.git
cd edge-proc
uv sync --all-extras # core + extras + dev tooling
uv run poe gate # lint + format-check + typecheck + complexity + test — what CI runs
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ on the device, EdgeProc can also search it there, with no network.

**Technical docs:** [Architecture](docs/ARCHITECTURE.md) · [Getting started for developers](docs/GETTING_STARTED.md) · [Full walkthrough with search](docs/QUICKSTART.md) · [Configuration](docs/CONFIGURATION.md) · [Operations and security](docs/OPERATIONS.md)

[![CI](https://github.com/hseshadr/edge-proc/actions/workflows/dagger.yml/badge.svg)](https://github.com/hseshadr/edge-proc/actions/workflows/dagger.yml)
[![CI](https://github.com/gainratio/edge-proc/actions/workflows/dagger.yml/badge.svg)](https://github.com/gainratio/edge-proc/actions/workflows/dagger.yml)
[![PyPI](https://img.shields.io/pypi/v/edge-proc)](https://pypi.org/project/edge-proc/)
[![License](https://img.shields.io/github/license/hseshadr/edge-proc)](LICENSE)
[![License](https://img.shields.io/github/license/gainratio/edge-proc)](LICENSE)

## Try it

Expand Down Expand Up @@ -135,7 +135,7 @@ rather than fetch one.

## How it fits with the related projects

- **[edgeproc-core](https://github.com/hseshadr/edgeproc-core)** (Python, on PyPI) is a
- **[edgeproc-core](https://github.com/gainratio/edgeproc-core)** (Python, on PyPI) is a
smaller library underneath this one. EdgeProc uses two things from it: a common interface
for vector search indexes, which EdgeProc's FAISS index implements, and the stable error
codes, like `bundle.integrity_failed`. It installs automatically with EdgeProc. You would
Expand All @@ -144,10 +144,10 @@ rather than fetch one.
pages) does the checking half of EdgeProc inside a browser tab. It reads the same signed
format that `edgeproc publish` writes, so you can publish with this library and load the
data in a web page.
- **[edge-reco](https://github.com/hseshadr/edge-reco)** is a demo online store
- **[edge-reco](https://github.com/gainratio/edge-reco)** is a demo online store
([edge-reco.com](https://edge-reco.com)) that runs product search in the shopper's
browser. It uses these libraries.
- **[privacy-core](https://github.com/hseshadr/privacy-core)** (published on npm as
- **[privacy-core](https://github.com/gainratio/privacy-core)** (published on npm as
`@edgeproc/privacy-core`) is a separate project despite the shared name. It hides card
numbers and similar IDs from AI prompts in the browser, and does not use EdgeProc.

Expand Down Expand Up @@ -196,7 +196,7 @@ This README documents EdgeProc 0.5.0. It needs Python 3.13 or newer and installs
## Develop

```bash
git clone https://github.com/hseshadr/edge-proc.git
git clone https://github.com/gainratio/edge-proc.git
cd edge-proc
uv sync --all-extras
uv run poe gate
Expand Down Expand Up @@ -229,6 +229,6 @@ setup, a map of the code, a first change, and how to open a pull request.
MIT. See [LICENSE](LICENSE). To cite EdgeProc, use [CITATION.cff](CITATION.cff).

EdgeProc (also written `edge-proc` and `edgeproc`) lives at
[hseshadr/edge-proc](https://github.com/hseshadr/edge-proc), with a project page at
[gainratio/edge-proc](https://github.com/gainratio/edge-proc), with a project page at
[edge-reco.com/edgeproc](https://edge-reco.com/edgeproc). It is not affiliated with any other
product or company named "EdgeProc".
2 changes: 1 addition & 1 deletion docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ builds on, installs from PyPI.
## Clone, install, and run it

```bash
git clone https://github.com/hseshadr/edge-proc.git
git clone https://github.com/gainratio/edge-proc.git
cd edge-proc
uv sync --all-extras
```
Expand Down
2 changes: 1 addition & 1 deletion docs/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Budget more wall-clock on a slow link. The model download happens once; later ru
## 1. Clone and gate

```bash
git clone https://github.com/hseshadr/edge-proc.git
git clone https://github.com/gainratio/edge-proc.git
cd edge-proc

uv sync --all-extras # core + [localvec] + [bundles] + dev tooling
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ dev = [
edgeproc = "edgeproc.cli:app"

[project.urls]
Homepage = "https://github.com/hseshadr/edge-proc"
Repository = "https://github.com/hseshadr/edge-proc"
Issues = "https://github.com/hseshadr/edge-proc/issues"
Homepage = "https://github.com/gainratio/edge-proc"
Repository = "https://github.com/gainratio/edge-proc"
Issues = "https://github.com/gainratio/edge-proc/issues"

# edgeproc-core installs from PyPI. Co-developing it locally? Clone it next to
# this repo and add a path override (uncomment):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_dagger_release_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def test_should_fetch_only_hosted_identity_fields(
monkeypatch.setattr(contract, "urlopen", lambda *_args, **_kwargs: next(responses))

# When
payload = contract.fetch_hosted_payload("hseshadr/edge-proc", SHA, "v1.2.3", "token")
payload = contract.fetch_hosted_payload("gainratio/edge-proc", SHA, "v1.2.3", "token")

# Then
contract.validate_hosted_eligibility(payload, SHA)
Expand Down
8 changes: 4 additions & 4 deletions tests/test_repin_locked_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def test_should_build_a_head_bound_commit_request(tmp_path: Path) -> None:

# When
request = tool.commit_request(
root, "hseshadr/edge-proc", "dependabot/uv/x", "c" * 40, (TEST_SITE,)
root, "gainratio/edge-proc", "dependabot/uv/x", "c" * 40, (TEST_SITE,)
)

# Then
Expand All @@ -161,7 +161,7 @@ def test_should_refuse_a_head_that_is_not_a_full_commit_sha(tmp_path: Path) -> N

# When / Then
with pytest.raises(tool.RepinError, match="commit sha"):
tool.commit_request(root, "hseshadr/edge-proc", "b", "HEAD", (TEST_SITE,))
tool.commit_request(root, "gainratio/edge-proc", "b", "HEAD", (TEST_SITE,))


def test_should_print_changed_sites_from_the_cli(
Expand Down Expand Up @@ -211,7 +211,7 @@ def test_should_exit_one_with_the_refusal_on_stderr(


HEAD = "e" * 40
REPOSITORY = "hseshadr/edge-proc"
REPOSITORY = "gainratio/edge-proc"


class FakeGitHub:
Expand Down Expand Up @@ -293,7 +293,7 @@ def test_should_write_nothing_when_the_head_literals_match_its_lock(tmp_path: Pa
("pull", "reason"),
[
(_pull(login="octocat"), "not opened by dependabot"),
(_pull(repo="fork/edge-proc"), "not in hseshadr/edge-proc"),
(_pull(repo="fork/edge-proc"), "not in gainratio/edge-proc"),
(_pull(ref="dependabot/npm_and_yarn/x"), "not a Dependabot uv branch"),
(_pull(ref=7), "malformed"),
],
Expand Down
4 changes: 2 additions & 2 deletions tests/test_workflow_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def _expand_action_args(args: str, tag: str, cwd: Path) -> list[str]:
"""Expand args exactly as dagger-for-github's final bash step does, but print them."""
bash = shutil.which("bash")
assert bash is not None
env = {"TAG": tag, "GITHUB_SHA": "a" * 40, "GITHUB_REPOSITORY": "hseshadr/edge-proc"}
env = {"TAG": tag, "GITHUB_SHA": "a" * 40, "GITHUB_REPOSITORY": "gainratio/edge-proc"}
env["PATH"] = "/usr/bin:/bin"
result = subprocess.run( # noqa: S603
[bash, "-c", f"printf '%s\\0' {args}"], env=env, cwd=cwd, capture_output=True, check=True
Expand Down Expand Up @@ -212,7 +212,7 @@ def test_should_pass_any_dispatched_tag_to_dagger_as_one_inert_argument(
# Then Dagger receives the tag verbatim as one argument and no command ran
assert words[:2] == ["release-candidate", f"--tag={tag}"]
assert words[2] == "--commit-sha=" + "a" * 40
assert words[4] == "--repository=hseshadr/edge-proc"
assert words[4] == "--repository=gainratio/edge-proc"
assert list(tmp_path.iterdir()) == []


Expand Down
Loading