Repository navigation
Conversation
PostgresDB joins JsonDB/SQLite/MongoDB/DynamoDB as a first-class
backend. Targets Neon, Aurora Serverless v2, and self-hosted PG 15+.
- jvspatial/db/postgres.py — asyncpg pool with auto-tuned sizing
(Lambda: 0/3, long-running: 2/10), session/transaction pooler modes,
schema-scoped tables, JSONB storage, vector column support, native
$near peeling, multi-tenant RLS via current_tenant contextvar,
transactional save/get/find/delete via PostgresTransaction, and
drop_deprecated_indexes for migrations.
- jvspatial/db/_postgres_translate.py — Mongo-style query → SQL
translator with positional-placeholder builder.
- jvspatial/db/factory.py — register "postgres"/"postgresql" with
env-driven defaults (DSN, pool sizes, pooler mode).
- jvspatial/db/transaction.py — backend-agnostic transaction_context.
- docker/ — local dev pgvector container + .env example.
- docs/md/postgres-guide.md, neon-deployment.md,
aurora-serverless-deployment.md, multi-tenant-rls.md,
cursor-pagination.md, schema-migrations.md, vector-store.md.
- tests/db/test_postgres_{unit,integration,translate}.py,
tests/db/test_cursor_pagination.py.
Companion changes pulled in:
- jvspatial/core/migrations.py + tests — record-level schema migrations.
- jvspatial/core/validate.py + tests — graph-shape validation.
- jvspatial/core/entities/walker_components/trail_store.py + tests —
pluggable walker trail persistence.
- jvspatial/api/auth/_session_store.py + tests — pluggable auth session
store (in-process / Redis) for multi-worker deployments.
- jvspatial/observability/tracing.py + tests — OTel span helpers.
- jvspatial/cli.py — `jvspatial migrate` CLI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Authlib-based OAuth 2.1 AS+RS layered on jvspatial's existing auth. RS256+JWKS access tokens, DCR, PRM/AS metadata, audience binding. Foundation for OAuth-secured MCP (M2 = Integral MCP endpoint, M3 = UI). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add OAuthSigningKey Object model and keys.py module: RSA-2048 keypair generation, persist/load via Object.find, idempotent ensure_signing_key(), and JWKS builder (public keys only). All three TDD tests pass. PyJWT 2.x to_jwk requires cryptography key object; PEM loaded via serialization.load_pem_public_key before conversion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add oauth_enabled, oauth_issuer_url, oauth_prefix, oauth_supported_scopes, oauth_dcr_enabled, oauth_access_token_ttl_minutes, oauth_code_ttl_seconds, and accept_oauth_bearer to AuthConfig. All default off/empty so existing apps are unaffected. Covered by two-test TDD suite in tests/api/auth/oauth/test_oauth_authconfig.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements OAuthClientAdapter wrapping OAuthClient so Authlib can validate redirect URIs, grant/response types, scope filtering, and token endpoint auth methods (public PKCE + confidential secret check). All three tests (redirect/grant checks, public auth method + scope filter, confidential secret check) pass; black/isort/flake8/mypy clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…io-bridged) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds RequiredS256CodeChallenge subclass that overrides the authorize-step hook to reject missing code_challenge (regardless of client auth method) and reject code_challenge_method=plain. Registers it in build_authorization_server instead of CodeChallenge(required=True), which only enforced PKCE for public clients (auth_method=="none"). Two regression tests added: confidential client without PKCE is rejected, and plain code_challenge_method is rejected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire _generate_refresh_token into JvSpatialJWTTokenGenerator so Authlib
emits an opaque rt_ token when client.check_grant_type("refresh_token")
is true; implement save_token to persist the plaintext (hashed) via
refresh_store.mint_refresh_token through a positional async shim
(_persist_refresh) compatible with call_async's positional-only contract.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-use code
Three hardening fixes from the M1b-2 spec §6:
1. scope∩permissions — `save_authorization_code` now reads `permissions`
from `request.user` (when the grant_user dict carries it) and stores
only the intersection of the client-allowed scope and the user's
permissions in `AuthorizationCode.scope`. Back-compat: callers that
pass `{"id":...}` without `permissions` keep the full client scope.
Because `create_token_response` reads scope from
`authorization_code.get_scope()`, the intersected scope propagates
to the token `scope` claim automatically with no generator changes.
2. nbf claim — `JvSpatialJWTTokenGenerator.get_extra_claims` overrides
the Authlib base (which returns `{}`) to inject `"nbf": int(time.time())`.
`access_token_generator` merges extra claims via `token_data.update(...)`,
so `nbf` lands in every issued JWT. super() is called first so any
future upstream additions are preserved.
3. atomic single-use — `query_authorization_code` calls `_consume_code`
(sets `consumed=True` + saves) *before* returning the `StoredAuthCode`
to the grant. A concurrent duplicate exchange sees `consumed=True` on
the first load and is rejected. `delete_authorization_code` remains
idempotent (Authlib still calls it after token issuance; the record is
already consumed at that point, which is harmless).
All 9 server-flow tests pass (incl. 2 new: scope-intersection+nbf,
back-compat no-permissions); all 24 OAuth tests pass.
…ckout) Move single-use enforcement from query_authorization_code (runs before redirect_uri + PKCE validation) to delete_authorization_code (runs only after all validation passes on the success path, per authlib 1.7.2 authorization_code.py:create_token_response). A request with a wrong code_verifier or wrong redirect_uri previously burned the code, locking out the legitimate client's retry. Now query_authorization_code is read-only; delete_authorization_code is the single-use point. Also corrects the atomicity docstring: single-use is serialized by the single-process anyio bridge, not a DB-level CAS; multi-worker deployment will require a conditional-update primitive (tracked for a later phase). Adds regression test test_failed_verifier_does_not_burn_code_for_retry proving a failed verifier does NOT lock out a correct retry, and confirms the existing single-use replay test still rejects a replayed code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements OAuth 2.1 §rotation family kill (review finding I-3). When a rotated/revoked refresh token is replayed, the entire rotation family is immediately revoked (including the live child token), and the request is rejected. Normal rotation is unaffected. - OAuthRefreshToken: +family_id field (shared across rotations of one grant) - refresh_store: mint_refresh_token gains family_id param; add find_any (lookup without is_active filter for replay detection) and revoke_family (kill all tokens in a family) - server.py: authenticate_refresh_token stashes rec.family_id on request._oauth_family_id; save_token forwards it to _persist_refresh so the rotated token inherits the family; fresh uuid4 family_id generated at first mint (auth-code path). Replay of revoked token triggers revoke_family.
Implements JvSpatialClientRegistrationEndpoint (open registration, MCP zero-config) + async_register_client on the authorization server. JSON body wired via BasicOAuth2Payload on request.payload before dispatching; public clients (auth_method=none) receive no client_secret in the 201 response and store no secret hash. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pure builder function ``build_as_metadata`` constructs the RFC 8414 Authorization Server metadata document (issuer, endpoints, PKCE, grant types). Validated against ``authlib.oauth2.rfc8414.AuthorizationServerMetadata`` in the accompanying TDD test. Route to serve the document at ``/.well-known/oauth-authorization-server`` is deferred to M1b-3b.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-limit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… key hook Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…known metadata-prefix bug) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… test build_as_metadata gains api_prefix kwarg (default "" for back-compat); the well-known handler now passes APIRoutes.PREFIX so token/registration/revocation/ authorization endpoints are advertised as https://as.example/api/oauth/… instead of missing the /api segment. register route pre-reads JSON body then builds a StarletteOAuth2Request with empty form to avoid double-read error. New test_oauth_http_flow.py covers both assertions over TestClient. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the blind `consumed = True; save()` in _consume_code with an atomic compare-and-swap (find_one_and_update predicated on consumed=False), mirroring the work_claim.py idiom. On a lost race (None result) the consume raises InvalidGrantError — the same error a replayed/consumed code produces via query_authorization_code — so the loser never gets a second token. Consume still runs after PKCE/redirect validation (failed verifier does not burn the code). Atomic on postgres (SELECT ... FOR UPDATE) and mongo (native findOneAndUpdate); best-effort on sqlite/json (no regression vs the prior blind write). Drop the consumed record from the GraphContext cache so a stale cached entry can't let a replay slip through. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7a — Drop the attacker-controlled User-Agent from the unauthenticated
rate-limit bucket key. The key is now the source IP alone (sha256-hashed):
folding in the UA let an attacker rotate the header per request to mint a
fresh bucket and evade the per-IP cap. Authenticated `user:{id}` path is
unchanged. (X-Forwarded-For trusted-proxy parsing remains deferred as 7b;
`request.client.host` stays as-is, documented as a known proxy limitation.)
3 — Declare `rate_limit_backend` as a real Optional[RateLimitBackend] field
on ServerConfig (default None -> process-local MemoryRateLimitBackend, behaviour
unchanged), replacing the prior getattr lookup. ServerConfig now allows
arbitrary types so the Protocol-typed backend can be stored. Documents the
multi-worker limitation (in-memory counter is per-process, so effective cap is
N x configured under N workers — including the DCR /oauth/register cap) on the
field, the server_configurator selection site, and _wire_dcr_rate_limit.
Tests: UA-independence of the identifier + same-IP shared bucket (no UA-rotation
evasion); rate_limit_backend field declared/defaults-None/accepts-instance and
server_configurator consumes a supplied backend (falls back to Memory otherwise).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Access tokens are stateless RS256 JWTs that previously stayed valid until their exp regardless of /oauth/revoke. Add a per-token jti denylist so a holder can revoke a specific access token before expiry: - OAuthRevokedToken Object (jti + self-expiring expires_at = token exp) - denylist.py store: revoke_jti (idempotent) / is_jti_revoked - resource.verify_oauth_access_token: require jti + reject denylisted jtis (Authlib's RFC-9068 generator always stamps jti; verified by decoding a freshly-minted token). One indexed lookup per RS validation (hot-path note; in-proc cache is a future optimization). - revocation endpoint: when an access-token JWT is presented (or token_type_hint=access_token), validate it via the same verify path and denylist its jti. Refresh-token revocation unchanged. Still requires client-auth + token possession (check_client on the client_id claim), so a caller can only revoke a token they hold. Per-token only; mass revocation (per-(user,client) revoked-after watermark) is a separate future item since stateless jtis can't be enumerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records the five M4 fixes (scope ceiling, DCR scope filter, auth-code CAS, IP-only rate-limit key + pluggable backend, jti denylist), the two intentional behavior changes (permissions:[] => deny scope; IP-only rate-limit key), the holistic adversarial review verdict (no over-grant on any branch, no fail-open, back-compat preserved, no cross-cutting bypass), and the two documented deferrals (Item 5 RFC-8707 per-request resource->aud; Item 7b trusted-XFF). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nsent ceiling fix 1bb5f5a) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ferences jvspatial is a generic application framework; remove the two remaining authoritative references to a specific downstream app (integral): - db/postgres.py: partial-filter docstring now says 'jvspatial and its downstream applications' instead of naming jvagent/integral - docs/md/multi-tenant-rls.md: RLS is 'a load-bearing isolation primitive for any multi-tenant platform built on it', not 'the Integral platform' Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mes in jvspatial) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nion to the COMPLETE record) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n and standard json backends
Make pyproject.toml the single source of build metadata and fix the metadata/packaging issues found in the pre-release review. Packaging: - Remove setup.py; resolve version dynamically via [tool.setuptools.dynamic] from jvspatial/version.py. Fixes a dual-source conflict where the wheel published Requires-Python >=3.8 (EOL) while the project targets >=3.9. - Add the `cache` extra (redis[hiredis]) so `pip install jvspatial[cache]` works; it backs jvspatial.cache.redis. - Complete the `all` extra: lambda, postgres, pgvector, otel, cache, scheduler. - Remove the redundant top-level requirements*.txt files; deps live solely in pyproject.toml extras. CI / release: - publish.yml: switch from API-token upload to PyPI Trusted Publishing (OIDC) via pypa/gh-action-pypi-publish; add id-token: write; drop dead setup.py/requirements config-change grep. - security.yml: drop the now-inert requirements*.txt path filters. Docs / templates: - CHANGELOG: promote [Unreleased] to [0.0.9] - 2026-06-14 with a Packaging section. - bug_report.md: drop downstream Jaclang/Jivas fields; add OS, DB backend, serverless mode. - Update workflow docs (README/VERSIONING/WORKFLOW_SUMMARY), RELEASING.md, and docs/md to drop setup.py/requirements*.txt references. Verified: clean sdist+wheel build, twine check PASSED, fresh-venv install + import + CLI all green; wheel METADATA shows version 0.0.9 and Requires-Python >=3.9. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x auth docs
Security:
- jvspatial/cache/redis.py: in the default `json` serialization mode the
deserializer fell through to pickle.loads on any unprefixed value, so
anyone able to write to the Redis keyspace could achieve RCE even with
the "safe" default. JSON mode now refuses non-JSON values (raised and
caught by get() as a cache miss -> recompute). Legacy pickle entries are
readable only via explicit opt-in (allow_legacy_pickle=True /
JVSPATIAL_REDIS_ALLOW_LEGACY_PICKLE=true) on a trusted keyspace.
Explicit `pickle` mode is unchanged.
Docs:
- README + docs/md/{authentication,api-keys,migration}.md used a flat
`auth_enabled=True` kwarg that ServerConfig silently ignores (auth
stayed disabled). Corrected to the nested `auth=dict(...)` form.
Cleanup:
- jvspatial/cache/layered.py: replace print() Redis-unavailable warnings
with logging.warning.
- .env.example: document JVSPATIAL_REDIS_ALLOW_LEGACY_PICKLE.
- tests: cover safe-default rejection, opt-in legacy read, and env toggle.
CHANGELOG: 0.0.9 Security/Fixed entries.
Two independent CI failures on the 0.0.9 PR.
1. Starlette >=0.52 broke auth/OpenAPI route introspection (4 failures).
Newer Starlette wraps `app.include_router(...)` in an `_IncludedRouter`
object instead of flattening the included APIRoutes into `app.routes`.
jvspatial's auth resolver only scanned top-level APIRoute instances, so
included routes (e.g. the file-storage GET/DELETE endpoints) looked
unregistered -> deny-by-default -> spurious 401s; OpenAPI security
padlocks were also dropped. Added `jvspatial/api/_route_utils.py`
(`iter_api_routes`) which recurses through `_IncludedRouter`
(`.original_router.routes`) and mounts, and routed every route-table
scan through it: endpoint auth resolver (3 sites), OpenAPI security
wiring, dynamic-walker registration, and duplicate-route detection.
Compatible with old and new Starlette. Reproduced and verified fixed
in a python:3.12-slim container (fastapi 0.137 / starlette 0.52);
does not regress the older local stack (fastapi 0.119 / starlette 0.48).
2. Postgres unit tests errored when asyncpg absent (14 failures).
`tests/db/test_postgres_unit.py` now `importorskip("asyncpg")` so it
skips cleanly instead of erroring; CI installs the postgres+pgvector
extras so the unit tests actually run (translator pushdown, pool
auto-tuning, vector encoding). Live-DB integration tests still skip
without JVSPATIAL_POSTGRES_TEST_DSN.
CHANGELOG: 0.0.9 Fixed entry for the Starlette compatibility fix.
Starlette 0.52.x carries PYSEC-2026-161 (fixed in 1.0.1); the previous `starlette<1.0.0` cap pinned installs to the vulnerable line and failed the pip-audit CI gate. The cap's original rationale (Starlette 1.0 drops Router on_startup/on_shutdown) does not apply: jvspatial wires lifecycle hooks through its own LifecycleManager (FastAPI lifespan), and route introspection is version-agnostic via _route_utils.iter_api_routes. Dependency is now `starlette>=0.46.0` (matches FastAPI's own floor, no upper cap). Fresh installs resolve to a patched Starlette (verified 1.3.1). Full suite green against Starlette 1.3.1; pip-audit reports no vulnerabilities with the CI-exact install.
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 25 to 27. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](dawidd6/action-download-artifact@v25...v27) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-version: '27' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Merged
|
Benchmark methodology changed. Capture a new main baseline before comparing timings. |
|
Benchmark methodology changed. Capture a new main baseline before comparing timings. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Applications can now capture bounded, read-only relationship, hierarchy, and ontology context for downstream language-model inference. This promotes the reviewed dev integration into main while retaining version 0.1.1 and keeping release assignment in separate PR #57.
The integration includes:
$existscriteria, isolated test contexts, and a real libmagic CI lane with valid file fixtures and unknown-binary rejection coverage.Validation:
The integration preserves direct MongoDB index declarations, the current missing/null query contract and content-based upload rejection. Incompatible proposals from old worktrees were not merged wholesale; original worktrees remain recoverable. See the integration assessment.
Local timings are regression evidence, not production throughput guarantees. Compatibility scans remain disclosed for backends without native projection paging. Model inference executes downstream.
Includes #53, #54, #55, subsequent rich graph-context work, and the qualified stalled-worktree fixes. Feature notes remain under Unreleased; separate draft #57 prepares 0.2.0 after this promotion.