Skip to content

Implement project management - #37

Merged
mitchins merged 4 commits into
mainfrom
feature/ui-project-key-management
Jul 21, 2026
Merged

mitchins merged 4 commits into
mainfrom
feature/ui-project-key-management

Conversation

@mitchins

@mitchins mitchins commented Jul 21, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added Web UI project management for creating/deleting projects and creating/revoking API keys.
    • Newly generated secrets are shown once with copy support; existing secrets are never displayed again.
    • Project/key identity is now reflected across request handling and project details.
    • Automation CLI remains available and follows the same locked management flow as the UI.
  • Documentation
    • Updated README and STATUS with one-time secret behavior, SHA-256 fingerprint/count-only presentation, and clarified UI rate-limit reporting.
  • Bug Fixes / Security
    • Tightened Web UI mutation request validation (strict origin/host and same-origin enforcement).
  • Tests
    • Added extensive unit coverage for project/key management, UI visibility rules, and CLI failure/refresh scenarios.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83329d68-f55a-4af8-88f8-c6af32e0d26a

📥 Commits

Reviewing files that changed from the base of the PR and between 4967ba4 and 6a51016.

📒 Files selected for processing (1)
  • smolrouter/facade_key_cli.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • smolrouter/facade_key_cli.py

📝 Walkthrough

Walkthrough

This change adds locked, file-backed project and facade-key management across storage, runtime registry, API, CLI, and Web UI. It also updates asynchronous identity resolution, key inventory presentation, documentation, and tests for validation, concurrency, recovery, authorization, and secret handling.

Changes

Project and facade-key management

Layer / File(s) Summary
Locked project-key storage and lifecycle
smolrouter/project_key_manager.py, tests/unit/test_project_key_manager.py
Adds validated project/key CRUD, fingerprints, locking, atomic replacement, snapshot refresh, conflict detection, rollback, fail-closed recovery, and lifecycle tests.
Runtime registry and identity integration
smolrouter/container.py, smolrouter/facade_key_cli.py, smolrouter/app.py, tests/unit/test_facade_key_cli.py
Routes default containers and CLI writes through ProjectKeyManager; registry access and identity resolution use asynchronous calls, and inventory exposes key fingerprints.
Strict project-management API
smolrouter/app.py, tests/unit/test_project_key_manager.py
Adds strict mutation bodies, same-origin checks, standardized errors, and asynchronous project/key endpoints with runtime authorization tests.
Projects management interface
smolrouter/templates/projects.html, smolrouter/templates/project_detail.html, smolrouter/templates/project_mutation.html, tests/unit/test_project_key_manager.py
Adds project creation, one-time secret display, fingerprint listing, key revocation, and confirmed project deletion without rendering stored secrets.
Operational documentation
README.md, STATUS.md
Documents UI and CLI management paths, key visibility, rate-limit presentation, and the revised identity-attribution roadmap.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant ProjectsUI
  participant FastAPI
  participant ProjectKeyManager
  participant FacadeKeyRegistry
  Operator->>ProjectsUI: submit project or key action
  ProjectsUI->>FastAPI: send same-origin JSON mutation
  FastAPI->>ProjectKeyManager: execute locked file-backed mutation
  ProjectKeyManager->>FacadeKeyRegistry: refresh runtime snapshot
  FacadeKeyRegistry-->>FastAPI: updated project/key state
  FastAPI-->>ProjectsUI: return mutation result
  ProjectsUI-->>Operator: display fingerprint or one-time secret
Loading

Possibly related PRs

  • mitchins/SmolRouter#27: Adds related facade-key identity and /projects integration that this project/key management work extends.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and directly reflects the main change: adding project management features and UI.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ui-project-key-management

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a thread- and process-safe, file-backed project and facade-key management system. It adds new API endpoints and Web UI controls to create and delete projects, as well as create and revoke facade API keys. Additionally, it integrates this management layer into the application container and CLI tool, backed by comprehensive unit tests. The review feedback highlights a potential cross-platform issue on macOS where calling fsync on a directory file descriptor might raise an OSError (such as EINVAL or ENOTSUP), and suggests catching and ignoring these specific errors to ensure compatibility.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +148 to +152
try:
os.fsync(directory_fd)
except OSError:
if os.name != "nt":
raise

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

On some platforms (such as macOS) or specific filesystems, calling fsync on a directory file descriptor is not supported and raises an OSError with EINVAL (Invalid argument). Since os.name != "nt" is checked, this error will be re-raised, causing all project and key mutation operations to fail on macOS. To ensure cross-platform compatibility, we should catch and ignore EINVAL, ENOTSUP, and EOPNOTSUPP errors during directory fsync.

Suggested change
try:
os.fsync(directory_fd)
except OSError:
if os.name != "nt":
raise
try:
os.fsync(directory_fd)
except OSError as exc:
import errno
if exc.errno not in {errno.EINVAL, errno.ENOTSUP, errno.EOPNOTSUPP}:
raise

@mitchins
mitchins marked this pull request as ready for review July 21, 2026 05:16

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
smolrouter/templates/project_detail.html (1)

251-256: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated JSON-mutation/error-handling logic across the two project templates. Both files independently re-implement the same fetch → response.json() → if (!response.ok) throw pattern for calling /api/project-management/*; extracting a shared helper (e.g., a small included <script> partial) would avoid drift as more mutation endpoints are added.

  • smolrouter/templates/project_detail.html#L251-L256: promote the existing mutate() helper into a shared script partial included by both templates.
  • smolrouter/templates/projects.html#L219-L224: replace the inline fetch/parse/throw block with a call to the shared mutate() helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@smolrouter/templates/project_detail.html` around lines 251 - 256, The JSON
mutation and error-handling logic is duplicated across both project templates.
Extract the existing mutate helper from smolrouter/templates/project_detail.html
lines 251-256 into a shared script partial included by both templates, then
update smolrouter/templates/projects.html lines 219-224 to call mutate() instead
of reimplementing fetch, JSON parsing, and error handling; preserve the current
behavior and signatures.
smolrouter/facade_key_cli.py (1)

122-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Handle ProjectKeyManagementError and source secret_count from the actual write.

manager.create_key(...) can raise ProjectKeyConflictError/ProjectKeyNotFoundError/OSError (e.g. concurrent routes change), which would surface as an unhandled traceback instead of the friendly exit-code style used earlier in run_create. Also, secret_count is printed from the stale in-memory updated computed before delegation rather than from what was persisted.

♻️ Suggested handling
-    manager = ProjectKeyManager(routes_path=routes_path, secrets_path=secret_file_path)
-    generated, _key_id = manager.create_key(project_id, generated)
+    manager = ProjectKeyManager(routes_path=routes_path, secrets_path=secret_file_path)
+    try:
+        generated, _key_id = manager.create_key(project_id, generated)
+    except ProjectKeyManagementError as exc:
+        print(f"Failed to create facade key: {exc}")
+        return 4
     print(f"Generated facade key for project: {project_id}")
     print(f"secret (copy exactly once): {generated}")
     print(f"wrote facade-key secrets to: {secret_file_path}")
-    print(f"secret_count: {len(updated.get(project_id, []))}")
+    print(f"secret_count: {len(manager.list_keys(project_id))}")
     return 0
(Requires importing `ProjectKeyManagementError` alongside `ProjectKeyManager`.)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@smolrouter/facade_key_cli.py` around lines 122 - 127, Update run_create
around ProjectKeyManager.create_key to catch ProjectKeyManagementError and exit
using the existing friendly error/exit-code handling pattern, including
conflicts, missing projects, and persistence OSError failures. Replace the stale
updated-based secret_count output with the count returned or otherwise exposed
by the successful persisted write, and import ProjectKeyManagementError
alongside ProjectKeyManager.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@smolrouter/facade_key_cli.py`:
- Around line 122-127: Update run_create around ProjectKeyManager.create_key to
catch ProjectKeyManagementError and exit using the existing friendly
error/exit-code handling pattern, including conflicts, missing projects, and
persistence OSError failures. Replace the stale updated-based secret_count
output with the count returned or otherwise exposed by the successful persisted
write, and import ProjectKeyManagementError alongside ProjectKeyManager.

In `@smolrouter/templates/project_detail.html`:
- Around line 251-256: The JSON mutation and error-handling logic is duplicated
across both project templates. Extract the existing mutate helper from
smolrouter/templates/project_detail.html lines 251-256 into a shared script
partial included by both templates, then update
smolrouter/templates/projects.html lines 219-224 to call mutate() instead of
reimplementing fetch, JSON parsing, and error handling; preserve the current
behavior and signatures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c27e6cf3-bdf7-4855-90b2-6af751247409

📥 Commits

Reviewing files that changed from the base of the PR and between 811ee3c and 120239f.

📒 Files selected for processing (9)
  • README.md
  • STATUS.md
  • smolrouter/app.py
  • smolrouter/container.py
  • smolrouter/facade_key_cli.py
  • smolrouter/project_key_manager.py
  • smolrouter/templates/project_detail.html
  • smolrouter/templates/projects.html
  • tests/unit/test_project_key_manager.py

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 120239f357

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +267 to +269
raw_projects = routes.get("facade_keys", {})
if not isinstance(raw_projects, dict):
raise ProjectKeyManagementError("routes facade_keys must be a mapping")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat blank facade_keys as empty config

When the routes file contains an empty placeholder like facade_keys: (YAML parses this as None), this check now raises ProjectKeyManagementError during default container startup because SmolRouterContainer() calls ProjectKeyManager.get_registry(). Previously SmolRouterConfig.__post_init__ normalized None facade keys to {}, so installs with no configured projects but a blank section stop serving until they hand-edit it to facade_keys: {}; treat None the same as a missing key here.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.53503% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.29%. Comparing base (811ee3c) to head (6a51016).

Files with missing lines Patch % Lines
smolrouter/project_key_manager.py 88.98% 23 Missing and 15 partials ⚠️
smolrouter/app.py 89.42% 10 Missing and 1 partial ⚠️
smolrouter/container.py 50.00% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #37      +/-   ##
==========================================
+ Coverage   75.66%   76.29%   +0.63%     
==========================================
  Files          37       38       +1     
  Lines       10280    10736     +456     
  Branches     1600     1653      +53     
==========================================
+ Hits         7778     8191     +413     
- Misses       1913     1943      +30     
- Partials      589      602      +13     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sonarqubecloud

Copy link
Copy Markdown

@mitchins
mitchins merged commit 38f4b08 into main Jul 21, 2026
12 checks passed
@mitchins
mitchins deleted the feature/ui-project-key-management branch July 21, 2026 06:25
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.

1 participant