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
51 changes: 51 additions & 0 deletions .cursor/rules/zest-project.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
description: Zest repository agent context and guardrails
alwaysApply: true
---

# Zest Agent Context

Read these first:

- `documentation/13-agent-brief.md`
- `documentation/14-decision-log.md`
- The subsystem doc linked from the agent brief for the files you are changing.

Hard rules:

- No runtime rule-based NOVA fallback.
- No AI image upload; OCR stays on device and backend receives text only.
- No user AI API keys; Gemini calls go through the backend proxy.
- No persistence or logging of scan images, OCR text, ingredients, results, chat, usage, failures, prompts, requests, or responses.
- Keep `/analyze` and `/chat` separate with separate backend-owned prompts.
- Android must not send prompt text, schemas, or model instructions.
- Keep allergens separate from ultra-processing markers.
- Keep module versions in root `module_versions.json`, root `VERSION_LOG.md`, and `documentation/12-module-versioning.md`.

Change governance:

- For every change, summarize what changed in the relevant documentation.
- Extract durable philosophy/architecture decisions and update `documentation/14-decision-log.md`.
- Update `documentation/13-agent-brief.md` when future agents need different guidance.
- Update root `module_versions.json` and root `VERSION_LOG.md` when behavior, contracts, prompts, build/release rules, documentation modules, or agent guidance changes.
- If no version bump is needed, explain why in the final response.

Current defaults:

- Android: Kotlin + Jetpack Compose.
- Backend: FastAPI on Cloud Run, Vertex AI service-account auth.
- Model: `gemini-3.5-flash`.
- Storage: session-only scan data; former Room/history code is archived under `documentation/code-archive/session_only_storage/`.

Verification:

```bash
./gradlew :app:verifySourceTreeForBuild :app:compileDebugKotlin
./gradlew :app:testDebugUnitTest
.venv/bin/python -m pytest backend/tests/test_app.py backend/tests/test_benchmark_analyze.py -q
git diff --check
```

Known caution:

Public Cloud Run `/analyze` and `/chat` access is acceptable only for testing or limited rollout. Broad production needs abuse controls.
55 changes: 55 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Zest Agent Instructions

Start every repo task by reading:

1. `documentation/13-agent-brief.md`
2. `documentation/14-decision-log.md`
3. The subsystem document linked from the brief for the area you are changing.

## Non-Negotiable Rules

- Do not add rule-based NOVA classification or fallback paths to runtime code.
- Do not send captured or imported images to any AI provider. OCR runs on device; the backend receives text only.
- Do not persist scan images, OCR text, normalized ingredients, results, chat, usage, or failures.
- Do not log OCR text, prompts, model requests/responses, user questions, or result context.
- Do not ask users for AI model API keys. Gemini calls go through the backend proxy using Cloud Run Vertex auth.
- Keep analysis and chat prompts backend-owned. Android must not send prompt text or schemas.
- Keep `/analyze` and `/chat` separate backend endpoints with separate prompts.
- Keep allergens separate from processing markers in UI and contracts.
- Keep module versions tracked in root `module_versions.json`, logged in root `VERSION_LOG.md`, and documented in `documentation/12-module-versioning.md`.

## Change Governance

Every change must leave a compact handoff trail:

- Summarize what changed in the relevant documentation.
- Extract the design philosophy or architecture decision behind the change and update `documentation/14-decision-log.md` when it creates or changes a durable decision.
- Update `documentation/13-agent-brief.md` when the change affects how future agents should approach the repo.
- Update root `module_versions.json` and root `VERSION_LOG.md` whenever behavior, public contracts, prompts, build/release rules, documentation modules, or agent guidance changes.
- If no version bump is needed, say why in the final response.

## Current Runtime Shape

- Android app: native Kotlin + Jetpack Compose.
- Image path: camera/gallery image -> on-device ML Kit OCR -> text-only backend `/analyze`.
- Barcode path: USDA lookup when configured -> backend `/analyze`.
- Chat path: current result + current session chat history -> backend `/chat`.
- Backend: FastAPI on Cloud Run, Vertex AI via service-account identity, default model `gemini-3.5-flash`.
- Storage: session-only scan data; former Room/history code is archived under `documentation/code-archive/session_only_storage/`.

## Verification Commands

Use the smallest relevant set first:

```bash
./gradlew :app:verifySourceTreeForBuild :app:compileDebugKotlin
./gradlew :app:testDebugUnitTest
.venv/bin/python -m pytest backend/tests/test_app.py backend/tests/test_benchmark_analyze.py -q
git diff --check
```

Gradle may need access to `~/.gradle` caches. If sandboxed execution fails on Gradle lock files, rerun with the approved Gradle command outside the sandbox.

## Known Production Caution

The deployed Cloud Run `/analyze` and `/chat` endpoints are documented as a temporary limited-rollout posture if publicly callable. Broad production needs an abuse-control layer such as Firebase App Check / Play Integrity, Firebase Auth, API Gateway/IAM, Cloud Armor, quotas, or rate limiting.
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Backend Cloud Run image. Build from the repository root so module_versions.json is packaged.
FROM python:3.12-slim

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PORT=8080

WORKDIR /app

COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY backend/main.py backend/prompt.py module_versions.json ./
COPY backend/prompts ./prompts

EXPOSE 8080

# Cloud Run sets PORT; default to 8080 locally.
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080}"]
35 changes: 35 additions & 0 deletions VERSION_LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Version Log

This is the repo-level change log for Zest module and release versions. Every code, prompt, architecture, documentation, or agent-guidance change must either add an entry here or explicitly state that no version bump was required.

Entries can represent coordinated product releases or independently versioned modules.

## 1.0.3

Date: 2026-07-13

- Optimized the backend full-analysis prompt contract for single-call NOVA decisioning, lower latency, and benchmark-driven accuracy.

## 1.0.2

Date: 2026-07-13

- Moved the module version manifest source of truth to the repo root: `module_versions.json`.
- Added repo-level version logging in `VERSION_LOG.md`.
- Added agent governance requiring every future change to summarize implementation, extract decisions/philosophy, and update the relevant documentation or decision file.
- Added documentation and agent-context modules to version tracking.
- Updated backend packaging to use the repo-root Docker build context and packaged root manifest for `/version`.

## 1.0.1

Date: 2026-07-13

- Switched the backend and Android model defaults from Gemini 2.5 Flash to `gemini-3.5-flash`.
- Updated backend deployment documentation and Android proxy fixtures for the Gemini 3.5 Flash default.
- Added module-version tracking and source-tree verification guard.

## 1.0.0

Date: 2026-07-13

- Baseline documented release after backend proxy, session-only storage, backend-owned prompts, separate `/analyze` and `/chat`, and no-user-AI-key architecture were established.
19 changes: 15 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ fun String.asBuildConfigStringLiteral(): String =

val releaseVersionCode = providers.gradleProperty("ZEST_VERSION_CODE")
.map(String::toInt)
.orElse(2)
.orElse(3)
val releaseVersionName = providers.gradleProperty("ZEST_VERSION_NAME")
.orElse("1.0.1")
.orElse("1.0.2")
val localProperties = Properties().apply {
val localPropertiesFile = rootProject.layout.projectDirectory.file("local.properties").asFile
if (localPropertiesFile.isFile) {
Expand Down Expand Up @@ -245,11 +245,15 @@ val verifyModuleVersionManifest = tasks.register("verifyModuleVersionManifest")
group = "verification"
description = "Fails if a Zest module is missing from the production version manifest or docs."
doLast {
val manifestFile = rootProject.file("backend/module_versions.json")
val manifestFile = rootProject.file("module_versions.json")
val versionLogFile = rootProject.file("VERSION_LOG.md")
val documentationFile = rootProject.file("documentation/12-module-versioning.md")
if (!manifestFile.isFile) {
throw GradleException("Missing module version manifest: ${manifestFile.relativeTo(rootDir)}")
}
if (!versionLogFile.isFile) {
throw GradleException("Missing repo version log: ${versionLogFile.relativeTo(rootDir)}")
}
if (!documentationFile.isFile) {
throw GradleException(
"Missing module version documentation: ${documentationFile.relativeTo(rootDir)}"
Expand All @@ -271,6 +275,10 @@ val verifyModuleVersionManifest = tasks.register("verifyModuleVersionManifest")
}
val moduleIds = mutableSetOf<String>()
val docsText = documentationFile.readText()
val versionLogText = versionLogFile.readText()
if (!versionLogText.contains("## $releaseVersion")) {
throw GradleException("VERSION_LOG.md is missing release entry: $releaseVersion")
}

fun moduleValue(module: Map<*, *>, key: String): String =
module[key] as? String
Expand All @@ -293,6 +301,9 @@ val verifyModuleVersionManifest = tasks.register("verifyModuleVersionManifest")
"Module version documentation is missing module $id version $version."
)
}
if (!versionLogText.contains(version)) {
throw GradleException("VERSION_LOG.md is missing module version $version for $id.")
}
if (kind != "android-gradle-module" && path.startsWith(":")) {
throw GradleException("Only android-gradle-module entries can use Gradle paths: $id")
}
Expand All @@ -312,7 +323,7 @@ val verifyModuleVersionManifest = tasks.register("verifyModuleVersionManifest")
val missingGradleModules = actualGradlePaths - manifestGradlePaths
if (missingGradleModules.isNotEmpty()) {
throw GradleException(
"Gradle modules missing from backend/module_versions.json: " +
"Gradle modules missing from module_versions.json: " +
missingGradleModules.joinToString()
)
}
Expand Down
8 changes: 5 additions & 3 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Backend Cloud Run image. Build with repository root as the Docker context:
# docker build -f backend/Dockerfile .
FROM python:3.12-slim

ENV PYTHONUNBUFFERED=1 \
Expand All @@ -6,11 +8,11 @@ ENV PYTHONUNBUFFERED=1 \

WORKDIR /app

COPY requirements.txt .
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py prompt.py module_versions.json ./
COPY prompts ./prompts
COPY backend/main.py backend/prompt.py module_versions.json ./
COPY backend/prompts ./prompts

EXPOSE 8080

Expand Down
4 changes: 3 additions & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ Corpus source: Open Food Facts public database and API.

```bash
gcloud run deploy ultraprocessed-ai-proxy \
--source backend \
--source . \
--project b2-ultra-processed \
--region us-east1 \
--service-account up-app-service@b2-ultra-processed.iam.gserviceaccount.com \
Expand All @@ -188,6 +188,8 @@ gcloud run deploy ultraprocessed-ai-proxy \

The service account must have Vertex AI access (e.g. `roles/aiplatform.user`) on the project.

Deploy from the repository root so the root `Dockerfile` and root `module_versions.json` are packaged into the backend image for `GET /version`. If using `backend/Dockerfile` directly, build with the repository root as the Docker context: `docker build -f backend/Dockerfile .`.

## Security notes

- **Known deferred risk:** `--allow-unauthenticated` leaves `/analyze` and `/chat` publicly callable. This is
Expand Down
10 changes: 8 additions & 2 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,17 @@
for origin in os.getenv("CORS_ALLOWED_ORIGINS", "").split(",")
if origin.strip()
]
MODULE_VERSION_MANIFEST_PATH = Path(__file__).with_name("module_versions.json")
MODULE_VERSION_MANIFEST_PATHS = (
Path(__file__).resolve().parent.parent / "module_versions.json",
Path(__file__).with_name("module_versions.json"),
)


def load_module_version_manifest() -> dict[str, Any]:
with MODULE_VERSION_MANIFEST_PATH.open(encoding="utf-8") as manifest_file:
manifest_path = next((path for path in MODULE_VERSION_MANIFEST_PATHS if path.is_file()), None)
if manifest_path is None:
raise RuntimeError("module version manifest not found")
with manifest_path.open(encoding="utf-8") as manifest_file:
manifest = json.load(manifest_file)
if not isinstance(manifest, dict):
raise RuntimeError("module version manifest must be a JSON object")
Expand Down
3 changes: 2 additions & 1 deletion documentation/07-testing-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ flowchart TB

- Unit tests pass.
- `verifySourceTreeForBuild` passes.
- `backend/module_versions.json` contains every Gradle module, top-level Android package module, backend service, and backend prompt contract.
- Root `module_versions.json` contains every Gradle module, top-level Android package module, backend service, backend prompt contract, documentation module, and agent-context module.
- Root `VERSION_LOG.md` contains the current release entry and the module versions changed by the release.
- Release APK assembles with signing environment variables present.
- Android test APK assembles.
- `rg "BuildConfig|local.properties|USDA_API_KEY" app/src/main app/build.gradle.kts` shows no embedded key source.
Expand Down
20 changes: 15 additions & 5 deletions documentation/12-module-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
Zest tracks module versions through one machine-readable manifest:

```text
backend/module_versions.json
module_versions.json
VERSION_LOG.md
```

That file is the production source of truth. It is packaged with the Cloud Run backend, exposed through `GET /version`, and checked by the Android release guard so new source modules cannot be added without an explicit version entry.
The root manifest is the production source of truth. The root version log records why release and module versions changed. The manifest is packaged with the Cloud Run backend, exposed through `GET /version`, and checked by the Android release guard so new source, documentation, or agent-guidance modules cannot be added without an explicit version entry.

## Current Module Versions

| Module ID | Kind | Path | Version | Runtime Surface |
| --- | --- | --- | --- | --- |
| `android-app` | android-gradle-module | `:app` | `1.0.1` | Android `BuildConfig.VERSION_NAME` |
| `backend-proxy` | cloud-run-service | `backend` | `1.0.1` | FastAPI `app.version` and `GET /version` |
| `android-app` | android-gradle-module | `:app` | `1.0.2` | Android `BuildConfig.VERSION_NAME` |
| `backend-proxy` | cloud-run-service | `backend` | `1.0.2` | FastAPI `app.version` and `GET /version` |
| `analysis` | android-package | `app/src/main/java/com/b2/ultraprocessed/analysis` | `1.0.1` | module version manifest |
| `barcode` | android-package | `app/src/main/java/com/b2/ultraprocessed/barcode` | `1.0.0` | module version manifest |
| `camera` | android-package | `app/src/main/java/com/b2/ultraprocessed/camera` | `1.0.0` | module version manifest |
Expand All @@ -24,20 +25,25 @@ That file is the production source of truth. It is packaged with the Cloud Run b
| `storage` | android-package | `app/src/main/java/com/b2/ultraprocessed/storage` | `1.0.0` | module version manifest |
| `ui` | android-package | `app/src/main/java/com/b2/ultraprocessed/ui` | `1.0.1` | module version manifest |
| `backend-prompts` | backend-contract-assets | `backend/prompts` | `1.0.3` | backend-owned prompt files |
| `documentation` | repo-documentation | `documentation` | `1.0.0` | repo documentation and handoff docs |
| `agent-context` | repo-agent-guidance | `AGENTS.md` | `1.0.0` | `AGENTS.md` and `.cursor/rules/zest-project.mdc` |

## Production Tracking Contract

- Every Gradle module must have one manifest entry with `kind = android-gradle-module`.
- Every top-level Android package under `app/src/main/java/com/b2/ultraprocessed` must have one manifest entry with `kind = android-package`.
- Every deployable backend service must have one manifest entry with a runtime endpoint that can report the deployed version.
- Backend prompt contracts are versioned separately from the backend service because prompt changes can alter model behavior without changing API transport.
- Documentation and agent guidance are versioned because they are now production handoff surfaces for future agents.
- Documentation must include each manifest module ID and version.
- `VERSION_LOG.md` must include the current `releaseVersion` and the module versions changed by that release.

## Verification

Android `verifySourceTreeForBuild` runs `verifyModuleVersionManifest`. The task fails when:

- `backend/module_versions.json` is missing or invalid,
- root `module_versions.json` is missing or invalid,
- root `VERSION_LOG.md` is missing the current release or changed module versions,
- a top-level Android package exists without a version entry,
- a version entry points to a missing path,
- a Gradle module exists without a manifest entry,
Expand All @@ -55,5 +61,9 @@ Backend tests verify:
- Bump `backend-proxy` when changing request validation, endpoint behavior, model configuration, response contracts, deployment config, auth, or observability.
- Bump `backend-prompts` when changing prompt instructions, schemas, examples, model-output expectations, or chat safety behavior.
- Bump an Android package module when changing public behavior owned by that package.
- Bump `documentation` when changing production docs, handoff docs, architecture guidance, or testing/release instructions.
- Bump `agent-context` when changing `AGENTS.md`, Cursor rules, or other agent bootstrap instructions.

Every change must also update the relevant documentation summary and extract any durable philosophy or architecture decision into `documentation/14-decision-log.md`. If the change affects how future agents should work, update `documentation/13-agent-brief.md`.

All module versions use semantic versioning. For coordinated releases, `releaseVersion` should match the app version shipped to users. Individual modules can move faster only when a change is internal and does not require a full app release.
Loading
Loading