Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d1edd79
feat(db): add PostgreSQL backend with serverless-tuned pooling
eldonm May 26, 2026
1a0feff
docs(spec): reusable OAuth 2.1 service design (M1)
eldonm Jun 3, 2026
426f80e
docs(plan): M1a OAuth foundation implementation plan
eldonm Jun 3, 2026
9482667
build(oauth): add cryptography dependency for RS256
eldonm Jun 3, 2026
f4d53e3
feat(oauth): OAuthClient + AuthorizationCode storage models
eldonm Jun 3, 2026
9183fb6
feat(oauth): RS256 signing-key store + JWKS
eldonm Jun 3, 2026
7cb380a
feat(oauth): AuthConfig OAuth fields (off by default)
eldonm Jun 3, 2026
bed4089
docs(spec): M1b design addendum β€” Authlib AS integration + jvspatial …
eldonm Jun 3, 2026
38b3f69
docs(plan): M1b-1 OAuth core authorization server plan
eldonm Jun 3, 2026
4f85283
build(oauth): add authlib dependency
eldonm Jun 3, 2026
822deaa
feat(oauth): anyio sync/async bridge + Starlette OAuth2Request wrapper
eldonm Jun 3, 2026
c0e92e3
feat(oauth): ClientMixin adapter over OAuthClient
eldonm Jun 3, 2026
b887458
feat(oauth): authorization-code+PKCE server issuing RS256 tokens (any…
eldonm Jun 3, 2026
fc785cb
fix(oauth): enforce mandatory S256 PKCE for all clients (security)
eldonm Jun 3, 2026
52f08d2
docs(plan): M1b-2 refresh grant + scope-intersection + token hardening
eldonm Jun 3, 2026
3a7178f
feat(oauth): OAuthRefreshToken model + refresh store
eldonm Jun 3, 2026
b7692d9
feat(oauth): issue + persist refresh tokens on auth-code flow
eldonm Jun 3, 2026
31265d7
feat(oauth): refresh-token grant with rotation + revocation
eldonm Jun 3, 2026
c751c22
fix(oauth): scope-permission intersection + nbf claim + atomic single…
eldonm Jun 3, 2026
23db9c5
fix(oauth): consume auth code only after full validation (no retry lo…
eldonm Jun 3, 2026
c1b0f74
docs(plan): M1b-3a DCR + revocation + reuse-detection + metadata
eldonm Jun 3, 2026
a8d329b
feat(oauth): refresh-token reuse detection (family revocation)
eldonm Jun 3, 2026
6d64033
feat(oauth): dynamic client registration (RFC 7591)
eldonm Jun 3, 2026
fba2382
feat(oauth): token revocation endpoint (RFC 7009)
eldonm Jun 3, 2026
68c1d8f
feat(oauth): RFC 8414 AS metadata builder
eldonm Jun 3, 2026
9618ca2
fix(oauth): DCR requires https redirect URIs (loopback http allowed)
eldonm Jun 3, 2026
4ecbbe8
docs(plan): M1b-3b OAuth HTTP wiring (routes + mount + consent + rate…
eldonm Jun 3, 2026
a30c4b0
feat(oauth): mount OAuth routers + well-known metadata/jwks + startup…
eldonm Jun 3, 2026
15a9cd1
docs: OAuth program resume handoff (M1b-3b Task 1 done; next steps + …
eldonm Jun 3, 2026
52fb3a3
fix(oauth): metadata advertises api-prefixed endpoints; DCR-over-HTTP…
eldonm Jun 3, 2026
eaae528
feat(oauth): /authorize consent + session-user permission resolution
eldonm Jun 3, 2026
b10adde
fix(oauth): rate-limit dynamic client registration (open-DCR abuse, I-1)
eldonm Jun 3, 2026
dc9879d
fix(oauth): /authorize deny path validates redirect_uri (no open redi…
eldonm Jun 3, 2026
cfe617f
docs(plan): M1c resource server (JWKS verifier + PRM + accept_oauth_b…
eldonm Jun 3, 2026
534eaf6
feat(oauth): resource-server access-token verifier
eldonm Jun 3, 2026
b510f05
feat(oauth): protected-resource metadata (RFC 9728) + 401 helper
eldonm Jun 3, 2026
e38511f
feat(oauth): accept OAuth bearer tokens on protected endpoints (accep…
eldonm Jun 3, 2026
f06dcbe
feat(oauth): emit WWW-Authenticate resource_metadata on 401 (RFC 9728…
eldonm Jun 3, 2026
8d99c27
docs: OAuth handoff β€” M1 complete (AS+RS), next = merge + M2/M3
eldonm Jun 3, 2026
7565955
feat(oauth): redirect unauthenticated GET /authorize to configured lo…
eldonm Jun 4, 2026
8fe442a
feat(oauth): supported-scope ceiling + empty-perms/wildcard scope har…
eldonm Jun 4, 2026
2ea86cd
feat(oauth): DCR filters requested scope against supported scopes
eldonm Jun 4, 2026
a3d5829
fix(oauth): atomic auth-code consume (CAS) to close multi-worker TOCTOU
eldonm Jun 4, 2026
9edf957
feat(ratelimit): IP-only identifier + first-class pluggable backend
eldonm Jun 4, 2026
fab089b
feat(oauth): jti denylist for access-token revocation (RFC 7009)
eldonm Jun 4, 2026
0d6e05b
docs(oauth): M4 hardening completion record + holistic security review
eldonm Jun 4, 2026
3a7beb6
docs(oauth): M4 record β€” cross-repo verification outcome (integral co…
eldonm Jun 4, 2026
1f4302e
docs: keep jvspatial implementation-agnostic β€” generalize integral re…
eldonm Jun 4, 2026
db89c2d
test(oauth): use generic app.example.com fixtures (no app-specific na…
eldonm Jun 4, 2026
5675b8f
docs(oauth): add M4 hardening implementation plan to .planning (compa…
eldonm Jun 4, 2026
e8a4283
feat: enable pretty printing for JSON serialization across both orjso…
Tharickv75 Jun 9, 2026
b156766
chore(release): clean up packaging for 0.0.9 PyPI release
eldonm Jun 14, 2026
b8680c4
fix(security): stop unpickling untrusted Redis blobs in json mode; fi…
eldonm Jun 14, 2026
f70ecf6
fix(api): restore route introspection under Starlette >=0.52; fix CI
eldonm Jun 14, 2026
2209937
fix(deps): lift Starlette cap to resolve PYSEC-2026-161
eldonm Jun 14, 2026
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ JVSPATIAL_CACHE_SIZE=1000
# Redis value encoding: json (default, safe if Redis could be untrusted) or pickle (legacy).
# JVSPATIAL_REDIS_SERIALIZATION=json

# Allow reading legacy (unprefixed pickle) values while in json mode. Default false.
# Enable ONLY on a fully trusted Redis -- pickle.loads on attacker-controlled bytes is RCE.
# JVSPATIAL_REDIS_ALLOW_LEGACY_PICKLE=false

# Include exception text in JSON for unhandled 500s (development only; default false).
# JVSPATIAL_EXPOSE_ERROR_DETAILS=false

Expand Down
7 changes: 4 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.

**Environment (please complete the following information):**
- jvspatial Version [e.g. 0.0.9]
- Python Version [e.g. 3.12]
- Jaclang Version [e.g. 0.7.27]
- Jivas Version [e.g. 2.0.0]
- jvspatial Version [e.g 0.0.1]
- OS [e.g. Ubuntu 24.04, macOS 14]
- Database backend [e.g. JSON, SQLite, PostgreSQL, MongoDB, DynamoDB]
- Serverless mode [yes / no]

**Additional context**
Add any other context about the problem here.
2 changes: 1 addition & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ If you prefer to use an API token instead:
- **Triggers:** Automatically runs on pushes to the `main` branch (including PR merges)
- **Source Code Detection:** Only triggers when source code files change:
- Python files in `jvspatial/` directory
- `setup.py`, `pyproject.toml`, `requirements*.txt`
- `pyproject.toml`
- `jvspatial/version.py` (version changes)
- **Ignores:** Documentation, examples, tests, and build artifacts
- **Tag Creation:** Automatically creates git tag if it doesn't exist
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/VERSIONING.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The workflow triggers when:

**Source code changes that trigger publishing:**
- Python files in `jvspatial/` directory
- `setup.py`, `pyproject.toml`, `requirements*.txt`
- `pyproject.toml`
- `jvspatial/version.py` (version changes)

**Files that do NOT trigger publishing:**
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/WORKFLOW_SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ The `publish.yml` workflow automatically creates git tags and publishes to PyPI
- Code is **pushed to `main` branch** (includes PR merges)
- **AND** source code files have changed:
- Python files in `jvspatial/` directory
- `setup.py`
- `pyproject.toml`
- `requirements*.txt` files
- `jvspatial/version.py`

### ❌ Does NOT Trigger When:
Expand Down
24 changes: 14 additions & 10 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write # Required to create and push git tags
id-token: write # Required for PyPI Trusted Publishing (OIDC)

steps:
- name: Checkout repository
Expand Down Expand Up @@ -57,7 +58,7 @@ jobs:
# Count source code changes (Python files in jvspatial/, excluding version.py)
SOURCE_CHANGES=$(echo "$CHANGED_FILES" | grep -E '^jvspatial/.*\.py$' | grep -v '^jvspatial/version.py$' | wc -l | tr -d ' ')
VERSION_CHANGED=$(echo "$CHANGED_FILES" | grep -q '^jvspatial/version.py$' && echo "true" || echo "false")
CONFIG_CHANGED=$(echo "$CHANGED_FILES" | grep -E '^(setup\.py|pyproject\.toml|requirements.*\.txt)$' | wc -l | tr -d ' ')
CONFIG_CHANGED=$(echo "$CHANGED_FILES" | grep -E '^pyproject\.toml$' | wc -l | tr -d ' ')

echo "source_changes=$SOURCE_CHANGES" >> $GITHUB_OUTPUT
echo "version_changed=$VERSION_CHANGED" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -151,16 +152,19 @@ jobs:
run: |
twine check dist/*

# Token-based publishing (alternative to trusted publishing)
# 1. Create an API token on PyPI: https://pypi.org/manage/account/token/
# 2. Add it as a GitHub secret named PYPI_API_TOKEN
# 3. Replace the placeholder values below with your actual token
# PyPI Trusted Publishing (OIDC) -- no long-lived API token needed.
# One-time setup on PyPI: Project -> Settings -> Publishing ->
# "Add a new pending publisher" with:
# Owner: TrueSelph
# Repo: jvspatial
# Workflow: publish.yml
# Env: (leave blank)
# The action mints a short-lived OIDC token at publish time; the
# `id-token: write` permission above authorizes it.
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
twine upload dist/*
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/

- name: Upload build artifacts
uses: actions/upload-artifact@v6
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ on:
pull_request:
paths:
- 'pyproject.toml'
- 'requirements*.txt'
- '.github/workflows/security.yml'
push:
branches:
- main
paths:
- 'pyproject.toml'
- 'requirements*.txt'
schedule:
# Mondays at 06:00 UTC. Catches CVEs disclosed over the weekend
# before adopters hit them.
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/test-jvspatial.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ jobs:
run: |
python -m pip install --upgrade pip

- name: Install jvspatial package with test and lambda dependencies
run: pip install -e '.[test,lambda]'
- name: Install jvspatial package with test, lambda, and postgres dependencies
# postgres/pgvector pull in asyncpg so the Postgres *unit* tests run in CI
# (translator pushdown, pool auto-tuning, vector encoding). Live-DB
# integration tests still skip without JVSPATIAL_POSTGRES_TEST_DSN.
run: pip install -e '.[test,lambda,postgres,pgvector]'

# Coverage on every matrix Python version spikes memory (~1.6k tests); the runner often OOM-kills
# the process (shown as "The operation was canceled"). Run full suite everywhere; enforce
Expand Down
36 changes: 36 additions & 0 deletions .planning/superpowers/OAUTH-HANDOFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# OAuth-secured MCP for Integral β€” Resume Handoff

**As of:** 2026-06-03. **Branch:** jvspatial `feat/oauth2-service` (HEAD `f06dcbe`, NOT merged to `dev`).
**Status:** **M1 COMPLETE** β€” reusable jvspatial OAuth 2.1 Authorization Server **+ Resource Server**, fully built, security-reviewed, HTTP-mounted, off by default. oauth suite **55 passed**; full `tests/api` green.

## Program (OAuth-secured MCP)
jvspatial gains a reusable OAuth 2.1 AS+RS; Integral uses it to secure an MCP server endpoint; the Agents settings UI surfaces it. External agents connect via MCP (BYOA = MCP-only). One resident harness = embedded jvagent cockpit.
**M1** jvspatial OAuth service βœ… β†’ **M2** Integral MCP endpoint (not started) β†’ **M3** Agents UI (not started).

## M1 β€” DONE (all on feat/oauth2-service, every phase TDD + spec + security review)
- **M1a** foundation: `oauth/models.py` (OAuthClient/AuthorizationCode/OAuthSigningKey/OAuthRefreshToken), `oauth/keys.py` (RS256 keystore+JWKS), AuthConfig oauth fields, cryptography dep.
- **M1b-1** AS core: Authlib server, authcode + **mandatory-S256 PKCE** β†’ RS256 RFC-9068 tokens, anyio sync/async bridge. (Critical PKCE-bypass fixed.)
- **M1b-2** refresh issuance + **rotation/revocation** + **reuse-detection (family)** + **scope∩permissions** + nbf + atomic single-use. (I-1 lockout, I-2 atomicity fixed.)
- **M1b-3a** `oauth/{dcr,revocation,metadata}.py` β€” DCR (RFC 7591, **https-only redirects**), revocation (RFC 7009), RFC 8414 metadata builder.
- **M1b-3b** `oauth/routes.py` + wiring β€” `/.well-known/{oauth-authorization-server,jwks.json}` at root, `/api/oauth/{authorize,token,register,revoke}` mounted, **consent + session-user permission resolution** (trust boundary), startup key hook, **DCR rate-limit** (I-1), metadata api-prefix fix, **deny-path open-redirect fixed**.
- **M1c** `oauth/resource.py` + middleware β€” RS256 access-token **verifier** (audience-bound, JWKS), **PRM** (RFC 9728) + **`WWW-Authenticate` 401 discovery**, **`accept_oauth_bearer`** middleware (OAuth tokens authorize `@endpoint(auth=True)` alongside session JWTs). Security-reviewed: audience binding, alg/key-confusion, synthesized-principal all verified secure (fails closed on role-gated endpoints).

Enable per app: `Server(auth=dict(auth_enabled=True, jwt_secret=..., oauth_enabled=True, oauth_issuer_url="https://host", oauth_supported_scopes=[...], accept_oauth_bearer=True))`.

## NEXT
1. **Merge M1** β€” `feat/oauth2-service` β†’ jvspatial `dev` (self-contained, off-by-default, full suite green). Review + FF/merge when ready.
2. **M2 β€” Integral MCP server endpoint** (repo `/Users/eldonmarks/Briefcase/dev/integral`). Plan: jvspatial `@endpoint`s driving `mcp` SDK `StreamableHTTPSessionManager.handle_request`, secured by M1c RS (`accept_oauth_bearer` + the verifier; serve PRM for the MCP resource), tools from `mcp_adapter` catalogue, in-process dispatch reusing `IntegralEmbeddedAction._call_endpoint`. Integral configures jvspatial oauth (`oauth_enabled`, `oauth_issuer_url`, `accept_oauth_bearer`). Spec/addendum in integral `.planning/`. Brainstorm β†’ spec β†’ plan β†’ build.
3. **M3 β€” Agents-settings MCP UI** (integral frontend `frontend/src/features/settings/sections/AgentsSection.tsx`): surface the MCP endpoint + connect instructions (with DCR, clients self-register β†’ thin UI). Task #16.

## OAuth backlog (deferred, tracked β€” not blockers)
- Per-request RFC-8707 `resource`β†’`aud` (today aud = issuer; single-process AS+RS). Needed if MCP resource URI β‰  issuer.
- Access-token `jti` denylist (stateless JWT; refresh revocation works; bound by exp).
- Multi-worker atomicity: single-use/rotation serialized by single-process anyio bridge; needs a DB conditional-update (CAS) primitive before horizontal scaling (review I-2).
- DCR rate-limit identifier is per-(IP+User-Agent) + no XFF (shared rate_limit middleware); tighten to IP-only + proxy XFF for the register override.
- Decide whether literal `scope=*` is grantable via OAuth (maps to permission-wildcard; never passes role gates).

## Key constraints (don't relitigate)
- Authlib core SYNC, jvspatial DB ASYNC-only β†’ anyio thread-bridge (`oauth/bridge.py`). No jvspatial-plumbing bypass (routes via AuthConfigurator/middleware, not raw ASGI mount).
- Authlib 1.7.2: `request.payload` (not deprecated `request.data`); `OAuth2Request.args/form` overridden in `oauth/requests.py`.
- Pre-commit: black/isort/flake8(D-codes)/mypy/detect-secrets. jvspatial `.planning/` IS git-tracked.
- Tests over HTTP need `AUTHLIB_INSECURE_TRANSPORT=1` (TestClient is http); prod enforces https.
Loading
Loading