Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
9b3fac2
0.0.9 (#23)
eldonm Jun 14, 2026
d3589f8
release: 0.0.10 — graph perf, identity map, postgres joins
eldonm Jul 2, 2026
f25507f
ci(deps): bump dawidd6/action-download-artifact from 6 to 21 (#15)
dependabot[bot] Jul 2, 2026
ea38251
ci(deps): bump actions/download-artifact from 5 to 8 (#16)
dependabot[bot] Jul 2, 2026
4377167
ci(deps): bump peter-evans/create-or-update-comment from 4 to 5 (#17)
dependabot[bot] Jul 2, 2026
d6e9724
Merge pull request #24 from TrueSelph/release/0.0.10
eldonm Jul 2, 2026
7ab74b8
ci(deps): bump actions/checkout from 5 to 6 (#18)
dependabot[bot] Jul 2, 2026
0100d72
ci(deps): bump actions/upload-artifact from 6 to 7 (#19)
dependabot[bot] Jul 2, 2026
c5b0a09
fix: connected-node fast path drops valid neighbors (0.0.11)
eldonm Jul 2, 2026
3c71445
fix: connected-node fast path drops valid neighbors (0.0.11) (#25)
eldonm Jul 2, 2026
9d93a55
fix: postgres translator hits entity/id/tenant_id columns, not JSONB …
eldonm Jul 4, 2026
aa6a7fb
fix: register JVSPATIAL_OBSERVABILITY_ENABLED/SLOW_QUERY_MS in env al…
eldonm Jul 4, 2026
3282480
ci(deps): bump actions/checkout from 6 to 7
dependabot[bot] Jul 6, 2026
d2c8c1c
Merge pull request #26 from TrueSelph/dependabot/github_actions/actio…
eldonm Jul 7, 2026
4473f62
Merge pull request #27 from TrueSelph/fix/postgres-translate-entity-c…
eldonm Jul 7, 2026
bc5fff7
chore(release): bump to 0.0.12; skip-existing on PyPI publish
eldonm Jul 7, 2026
07f5d06
Merge pull request #28 from TrueSelph/chore/release-0.0.12-skip-existing
eldonm Jul 7, 2026
2a620b3
ci(deps): bump actions/setup-python from 6 to 7
dependabot[bot] Jul 20, 2026
0574875
fix(serverless): strict scheduling raises on dispatch failure, not ju…
Jul 31, 2026
2c48fa6
fix(db): resolve dotted field paths in in-memory find sort
eldonm Jul 31, 2026
bb024ee
fix(db): place records missing the sort field last when sorting desce…
eldonm Jul 31, 2026
96c1c8a
docs(spec): document the find() sort contract adapters must match
eldonm Jul 31, 2026
2166da8
fix(db): postgres withholds LIMIT when the sort falls back to memory
eldonm Jul 31, 2026
c6ba22b
fix(core): find_page resolves dotted cursors and reaches the missing-…
eldonm Jul 31, 2026
1ddf35b
fix(core): pager in-page re-sort agrees with the database slice
eldonm Jul 31, 2026
96f13fd
docs: correct stale NULL-ordering claims and scope the find sort cont…
eldonm Jul 31, 2026
92116aa
test(db): sqlite sort e2e for both branches; normalize empty sort spec
eldonm Jul 31, 2026
dd1c21a
fix(serverless): strict dispatch covers every failure path and every …
eldonm Jul 31, 2026
53b3a9c
ci(security): upgrade setuptools before pip-audit resolves the dep set
eldonm Jul 31, 2026
6dd4abd
Merge pull request #29 from TrueSelph/dependabot/github_actions/actio…
eldonm Jul 31, 2026
e39036b
Merge pull request #30 from TrueSelph/fix/strict-scheduler-failures
eldonm Jul 31, 2026
255160d
Merge branch 'main' into fix/find-sort-dotted-path
eldonm Jul 31, 2026
46e04f0
Merge pull request #31 from TrueSelph/fix/find-sort-dotted-path
eldonm Jul 31, 2026
7c407af
Version bump
barnwell Jul 31, 2026
dba1d31
Merge pull request #33 from TrueSelph/jb/dev
barnwell Jul 31, 2026
f70046f
fix: implement SQLite partial unique indexes to prevent session_id co…
Tharickv75 Jul 31, 2026
6016add
Version bump
barnwell Jul 31, 2026
4f94c8a
Merge pull request #32 from TrueSelph/fix/sqlite-partial-unique-index
barnwell Jul 31, 2026
3fd09ca
fix: enforce authorization for deferred-invoke endpoint
barnwell Jul 31, 2026
3c2aad7
chore: update version to 0.0.15 and enhance CHANGELOG
barnwell Jul 31, 2026
b6f3d27
Merge pull request #34 from TrueSelph/jb/dev
barnwell Jul 31, 2026
eb30781
Rebuild the Postgres pool when the event loop changes
eldonm Aug 4, 2026
8b73906
Support postgres as a Server database type
eldonm Aug 4, 2026
dce9a87
Merge pull request #35 from TrueSelph/api/postgres-server-support
eldonm Aug 4, 2026
9c82588
Release 0.0.16
eldonm Aug 4, 2026
3bed558
Merge pull request #36 from TrueSelph/release/0.0.16
eldonm Aug 5, 2026
cb22d19
Accept DatabaseConfig values by field name, not only by env alias
eldonm Aug 5, 2026
30ed488
Merge pull request #37 from TrueSelph/api/database-config-populate-by…
eldonm Aug 5, 2026
4c93d95
Release 0.0.17
eldonm Aug 5, 2026
9068ee1
Merge pull request #38 from TrueSelph/release/0.0.17
eldonm Aug 7, 2026
0b743d1
Cap uvicorn below 1.0
eldonm Aug 7, 2026
76397ef
Merge pull request #42 from TrueSelph/chore/cap-uvicorn
eldonm Aug 8, 2026
8fc5138
fix: honor JVSPATIAL_WEBHOOK_API_KEY_REQUIRE_HTTPS in env adapter
eldonm Aug 12, 2026
be36cb7
ci(deps): bump dawidd6/action-download-artifact from 21 to 24
dependabot[bot] Aug 24, 2026
23af4c0
bench: add hub-node scale recorder and 0.0.17 baseline
eldonm Sep 11, 2026
dc2a6cc
Derive node adjacency from the edge table on Postgres, MongoDB, SQLite
eldonm Sep 11, 2026
e7dfe6f
Push neighbour filters, limits, counts and pages into the database
eldonm Sep 11, 2026
905d122
fix(db): wrappers forward bulk_save_detailed to the backend
eldonm Sep 11, 2026
844a951
Entity-leading Postgres indexes, optional GIN, $text search
eldonm Sep 11, 2026
51186f1
fix(postgres): honour tenant scope on every path; command timeout env
eldonm Sep 11, 2026
380af22
Release 0.0.18 — hub-node scale remediation
eldonm Sep 13, 2026
b2e4cb1
chore: keep hub-node fixtures and docs domain-agnostic
eldonm Sep 13, 2026
19c242d
Merge pull request #44 from TrueSelph/release/0.0.18-hub-node
eldonm Sep 13, 2026
1046185
Merge pull request #43 from TrueSelph/dependabot/github_actions/dawid…
eldonm Sep 14, 2026
71aee76
release: jvspatial 0.0.19 derive-only node adjacency
eldonm Sep 14, 2026
696d314
docs: design Object.create_if_absent atomic insert
eldonm Sep 18, 2026
0140387
feat(db): add InsertIfAbsentResult and Database.insert_if_absent ABC
eldonm Sep 18, 2026
bccefb2
feat(db): implement insert_if_absent on built-in adapters and wrappers
eldonm Sep 18, 2026
bfbeacb
feat(core): add Object.create_if_absent atomic create-or-return
eldonm Sep 18, 2026
4138372
test: cover insert_if_absent and Object.create_if_absent
eldonm Sep 18, 2026
8fe796e
docs: ship create_if_absent in 0.0.20 notes and references
eldonm Sep 18, 2026
c27107b
fix(db): capitalize DynamoDB insert_if_absent docstring for D403
eldonm Sep 18, 2026
b28a9d4
docs: plan Object.create_if_absent implementation
eldonm Sep 18, 2026
b808541
Merge pull request #45 from TrueSelph/feat/object-create-if-absent
eldonm Sep 19, 2026
3d9acd4
feat(db): add PostgresTransaction.find_one_and_update
eldonm Sep 19, 2026
65fe03a
Merge pull request #46 from TrueSelph/feat/txn-find-one-and-update
eldonm Sep 19, 2026
0a414c6
Bind Node writes to the open transaction so a rollback undoes the graph.
eldonm Sep 27, 2026
6cc1122
Bump version to 0.0.22
eldonm Sep 27, 2026
cc7109a
Merge pull request #47 from TrueSelph/feat/graph-transaction
eldonm Sep 27, 2026
bd28c38
Create a GitHub Release when a new version tag is cut.
eldonm Sep 27, 2026
15cb83d
Merge pull request #48 from TrueSelph/chore/github-release-on-publish
eldonm Sep 27, 2026
4dadd1f
fix: remediate combined security review findings
eldonm Sep 27, 2026
b463095
fix: preserve secure defaults with explicit consumer compatibility
eldonm Sep 27, 2026
948635b
fix: distinguish missing route metadata from resolver failures
eldonm Sep 27, 2026
60b6951
Document security compatibility contracts
eldonm Sep 27, 2026
2f6504a
Make AGENTS.md the canonical agent guide
eldonm Sep 27, 2026
34a80ec
chore(release): prepare jvspatial 0.1.0
eldonm Sep 27, 2026
e281236
Harden 0.1.0 regex, auth routing, and webhook idempotency
eldonm Sep 27, 2026
ad5817f
Encrypt OAuth signing keys at rest when configured
eldonm Sep 27, 2026
dd3702f
Require OAuth key encryption in production mode
eldonm Sep 27, 2026
761b8ce
Fix cross-backend query and transaction release regressions
eldonm Sep 27, 2026
fe54330
Make SQLite rebind regression portable across aiosqlite versions
eldonm Sep 27, 2026
87dd0c7
Merge pull request #49 from TrueSelph/codex/security-review-remediation
eldonm Sep 27, 2026
0b2611a
ci(deps): bump dawidd6/action-download-artifact from 24 to 25 (#50)
dependabot[bot] Oct 3, 2026
a1f42e4
Fix PostgreSQL index names and bootstrap races (#51)
eldonm Oct 3, 2026
f645f20
Release 0.1.1 (#52)
eldonm Oct 3, 2026
2a51d67
ci(deps): bump dawidd6/action-download-artifact from 25 to 27
dependabot[bot] Oct 5, 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
8 changes: 7 additions & 1 deletion .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 Expand Up @@ -390,7 +394,9 @@ JVSPATIAL_LOG_DB_PATH=./jvspatial_logs
# JVSPATIAL_AWS_SQS_QUEUE_URL=
# Optional: skip mounting POST …/_internal/deferred (e.g. alternate worker entry)
# JVSPATIAL_DEFERRED_INVOKE_DISABLED=false
# Optional: require X-JVSPATIAL-Deferred-Authorize or Authorization: Bearer …
# Required for non-loopback callers of POST …/_internal/deferred (header
# X-JVSPATIAL-Deferred-Authorize or Authorization: Bearer …). LWA self-invoke
# from 127.0.0.1 is always allowed without this secret.
# JVSPATIAL_DEFERRED_INVOKE_SECRET=
# Work-claim lease TTL for persistent background work (seconds)
# JVSPATIAL_WORK_CLAIM_STALE_SECONDS=600
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
32 changes: 24 additions & 8 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,25 @@ jobs:
run-benchmarks:
name: Run benchmark suite
runs-on: ubuntu-latest
services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: jvspatial
POSTGRES_PASSWORD: jvspatial
POSTGRES_DB: jvspatial
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U jvspatial"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: '3.11'

Expand All @@ -56,6 +70,8 @@ jobs:
pip install -e '.[dev,test]'

- name: Run benchmarks (PR or main push)
env:
JVSPATIAL_POSTGRES_TEST_DSN: postgresql://jvspatial:jvspatial@localhost:5432/jvspatial
run: |
mkdir -p .benchmarks/current
pytest tests/benchmarks \
Expand All @@ -65,7 +81,7 @@ jobs:
--benchmark-warmup=on

- name: Upload current results
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: bench-current
path: .benchmarks/current/result.json
Expand All @@ -74,7 +90,7 @@ jobs:
# On a push to main, this run becomes the new baseline.
- name: Publish as baseline (main only)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: bench-baseline
path: .benchmarks/current/result.json
Expand All @@ -86,18 +102,18 @@ jobs:
runs-on: ubuntu-latest
needs: run-benchmarks
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7

- name: Download current results
uses: actions/download-artifact@v5
uses: actions/download-artifact@v8
with:
name: bench-current
path: .benchmarks/current

- name: Download baseline (best-effort)
id: baseline
continue-on-error: true
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@v27
with:
workflow: benchmarks.yml
branch: main
Expand Down Expand Up @@ -179,7 +195,7 @@ jobs:

- name: Comment on PR
if: steps.have_baseline.outputs.present == 'true'
uses: peter-evans/create-or-update-comment@v4
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ github.event.pull_request.number }}
body-path: comment.md
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
working-directory: ./
steps:
- name: Check out code
uses: actions/checkout@v5
uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: '3.12'

Expand Down
45 changes: 32 additions & 13 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ 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
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: '3.11'

Expand All @@ -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 @@ -135,6 +136,17 @@ jobs:
git push origin "$TAG_NAME"
echo "Created and pushed tag: $TAG_NAME"

- name: Create GitHub Release
if: steps.check_tag.outputs.tag_exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ steps.get_version.outputs.tag_name }}" \
--title "jvspatial ${{ steps.get_version.outputs.version }}" \
--generate-notes \
--latest
echo "Created GitHub Release ${{ steps.get_version.outputs.tag_name }}"

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
Expand All @@ -151,19 +163,26 @@ 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/
# No-op (not a failure) when this version already exists on PyPI, so
# merges to main that don't bump the version don't red-X the branch.
# A real publish only happens when jvspatial/version.py is bumped.
skip-existing: true

- name: Upload build artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: failure()
with:
name: dist
Expand Down
20 changes: 15 additions & 5 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 All @@ -31,16 +29,28 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: '3.11'

- name: Install project + audit tooling
run: |
python -m pip install --upgrade pip
# setuptools is upgraded explicitly, not just pip. The runner
# image ships whatever setuptools its Python bundled, that
# version lands in the freeze list below, and the audit then
# reports a CVE in a build tool jvspatial neither declares as a
# runtime dependency nor vendors into its wheel (PYSEC-2026-3447
# in setuptools 79.0.1, fixed in 83.0.0).
#
# Deliberately NOT floored in pyproject's [build-system]
# requires: setuptools 83 needs Python >=3.10 while jvspatial
# supports >=3.9, so pinning it there makes a source build
# unresolvable on 3.9. This job pins 3.11, so upgrading here is
# safe and keeps the audit signal about *our* dependencies.
python -m pip install --upgrade pip setuptools
pip install pip-audit
# Resolve the project's full dependency set so the audit
# sees the same packages adopters would install.
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/test-jvspatial.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@ jobs:
python-version: ['3.10', '3.11', '3.12']
steps:
- name: Check out code
uses: actions/checkout@v5
uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
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