diff --git a/.agent/project.md b/.agent/project.md index 50156f9..4530b05 100644 --- a/.agent/project.md +++ b/.agent/project.md @@ -6,13 +6,6 @@ Supervaizer is the open-source controller library and FastAPI server that AI age This repo is public and packaged for external users, so API compatibility, typed payloads, generated documentation, and clear tests matter more than local convenience. -## Local Glossary / Terminology - -- **Controller**: The Supervaizer FastAPI service embedded with or run alongside an AI agent. -- **Studio**: The Supervaize SaaS platform that receives registration/events and operates mapped agents. -- **Mapped agent**: An agent capability exposed through Supervaizer for Studio control. -- **Registration payload**: The `server.register` payload consumed by Studio. - ## Data Sensitivity Notes - Do not log or commit API keys, server secrets, workspace tokens, or customer data. diff --git a/.claude/skills/gitnexus/gitnexus-cli/SKILL.md b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md index 989c082..b73ea7e 100644 --- a/.claude/skills/gitnexus/gitnexus-cli/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md @@ -24,6 +24,7 @@ Run from the project root. This parses all source files, builds the knowledge gr | `--force` | Force full re-index even if up to date | | `--embeddings` | Enable embedding generation for semantic search (off by default) | | `--drop-embeddings` | Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` preserves them. | +| `--pdg` | Build the program-dependence layers used by `explain` and `pdg_query` (taint, CDG, and REACHING_DEF). | **When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook detects staleness after `git commit` and `git merge` and notifies the agent to run `analyze` — the hook does not run analyze itself, to avoid blocking the agent for up to 120s and risking KuzuDB corruption on timeout. diff --git a/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md index 9834f94..4a33e58 100644 --- a/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md @@ -16,10 +16,10 @@ description: "Use when the user is debugging a bug, tracing an error, or asking ## Workflow ``` -1. query({query: ""}) → Find related execution flows +1. query({search_query: ""}) → Find related execution flows 2. context({name: ""}) → See callers/callees/processes 3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow -4. cypher({query: "MATCH path..."}) → Custom traces if needed +4. cypher({statement: "MATCH path..."}) → Custom traces if needed ``` > If "Index is stale" → run `node .gitnexus/run.cjs analyze` in terminal. @@ -45,13 +45,14 @@ description: "Use when the user is debugging a bug, tracing an error, or asking | Intermittent failure | `context` → look for external calls, async deps | | Performance issue | `context` → find symbols with many callers (hot paths) | | Recent regression | `detect_changes` to see what your changes affect | +| "How does A reach B?" | `trace` between the two symbols — shortest call chain in one call | ## Tools **query** — find code related to error: ``` -query({query: "payment validation error"}) +query({search_query: "payment validation error"}) → Processes: CheckoutFlow, ErrorHandling → Symbols: validatePayment, handlePaymentError, PaymentException ``` @@ -72,10 +73,21 @@ MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "valid RETURN [n IN nodes(path) | n.name] AS chain ``` +**trace** — shortest call chain between two symbols ("how does A reach B?"), one call instead of chaining `context` hops: + +``` +trace({ from: "processCheckout", to: "fetchRates" }) +→ status: ok, hopCount: 3 +→ hops: processCheckout → validatePayment → verifyCard → fetchRates +→ edges: CALLS (1.0), CALLS (0.95), CALLS (1.0) +``` + +When no path exists, `trace` reports the furthest reachable node — exactly where the chain breaks (dynamic dispatch, reflection, or an external boundary). + ## Example: "Payment endpoint returns 500 intermittently" ``` -1. query({query: "payment error handling"}) +1. query({search_query: "payment error handling"}) → Processes: CheckoutFlow, ErrorHandling → Symbols: validatePayment, handlePaymentError diff --git a/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md index ccf684c..f483c2f 100644 --- a/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md @@ -18,7 +18,7 @@ description: "Use when the user asks how code works, wants to understand archite ``` 1. READ gitnexus://repos → Discover indexed repos 2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness -3. query({query: ""}) → Find related execution flows +3. query({search_query: ""}) → Find related execution flows 4. context({name: ""}) → Deep dive on specific symbol 5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow ``` @@ -50,7 +50,7 @@ description: "Use when the user asks how code works, wants to understand archite **query** — find execution flows related to a concept: ``` -query({query: "payment processing"}) +query({search_query: "payment processing"}) → Processes: CheckoutFlow, RefundFlow, WebhookHandler → Symbols grouped by flow with file locations ``` @@ -68,7 +68,7 @@ context({name: "validateUser"}) ``` 1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes -2. query({query: "payment processing"}) +2. query({search_query: "payment processing"}) → CheckoutFlow: processPayment → validateCard → chargeStripe → RefundFlow: initiateRefund → calculateRefund → processRefund 3. context({name: "processPayment"}) diff --git a/.claude/skills/gitnexus/gitnexus-guide/SKILL.md b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md index a543378..c966161 100644 --- a/.claude/skills/gitnexus/gitnexus-guide/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md @@ -35,9 +35,19 @@ For any task involving code understanding, debugging, impact analysis, or refact | `query` | Process-grouped code intelligence — execution flows related to a concept | | `context` | 360-degree symbol view — categorized refs, processes it participates in | | `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence | +| `trace` | Shortest path between two symbols — "how does A reach B?" in one call | | `detect_changes` | Git-diff impact — what do your current changes affect | | `rename` | Multi-file coordinated rename with confidence-tagged edits | | `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) | +| `explain` | Persisted taint findings — source→sink data flows (needs `analyze --pdg`) | +| `pdg_query` | Control/data dependence — what gates X (CDG) / where Y flows (REACHING_DEF); needs `analyze --pdg` | +| `check` | Check graph invariants such as circular imports | +| `route_map` | API route map — which components/hooks fetch which endpoints, and the handler files that serve them | +| `shape_check` | Response-shape drift — keys each route returns vs keys its consumers access (flags MISMATCH) | +| `api_impact` | Pre-change report for an API route — consumers, middleware, shape mismatches, risk level | +| `tool_map` | MCP/RPC tool definitions and the files that handle them | +| `group_list` | List configured multi-repo groups, or one group's config | +| `group_sync` | Rebuild a group's Contract Registry (cross-repo HTTP contract links); run after `group.yaml` changes or member re-index | | `list_repos` | Discover indexed repos (paginated — `limit`/`offset`) | ### Paginating `list_repos` @@ -71,6 +81,37 @@ list_repos { offset: 400 } → repos 401–437, hasMore false Notes: `offset` ≥ `total` returns an empty page (with `total` still reported). Out-of-range or malformed `limit`/`offset` (non-integer, `limit` outside `[1, 200]`, `offset < 0`) are rejected with a clear error — `limit` above the max is rejected, not silently capped. The order is deterministic (lower-cased name, then path), so paging never skips or duplicates an entry while the registry is unchanged. +### Taint findings (`explain`) + +`explain` returns taint findings recorded by `gitnexus analyze --pdg` — intra-procedural `TAINTED` edges plus cross-function `TAINT_PATH` hops where the interprocedural taint phase found a function-level source→sink chain. Each finding includes a sink category (command-injection, code-injection, path-traversal, sql-injection, xss), source/sink lines, and the ordered hop path with the variable carried on each hop. + +- `explain {}` — enumerate all findings for the repo (bounded by `limit`, deterministic order) +- `explain { target: "src/vuln.ts" }` — findings in a file (suffix path match accepted) +- `explain { target: "runUserCommand" }` — findings in a function (resolved like `context`; ambiguous names return ranked candidates) + +A repo indexed without `--pdg` returns a clear "no taint layer" note. Caveats: closure/callback, property/field, and implicit flows are not modeled, and interprocedural findings are function-level `TAINT_PATH` hops rather than statement-level path proof, so the absence of a finding is **not** proof of safety. `SANITIZES` (sanitizer-kill) edges are queryable via `cypher`. + +### Control & data dependence (`pdg_query`) + +`pdg_query` reads the control/data-dependence layers `gitnexus analyze --pdg` records (CDG + REACHING_DEF, basic-block granular) — the control/data analog of `explain`. It is **always anchored** (a `target` file path or symbol, resolved like `context`) and has two modes: + +- `pdg_query { mode: "controls", target: "..." }` — CDG: "under what condition does X run?". Each edge is a controlling predicate block → dependent block with the branch sense (`'T'`/`'F'`) in `reason`; an edge into an early `return`/`throw` is flagged `guard: true` (guard-clause discovery — the sense depends on the predicate, so don't filter guards by a fixed label). +- `pdg_query { mode: "flows", target: "...", variable?: "..." }` — REACHING_DEF def→use edges within the function; pass `variable` to trace one binding. + +A repo indexed without `--pdg` returns a "no PDG layer" note (or "status unknown" when the layer can't be confirmed). Intra-procedural only — cross-function flow is taint's domain (`explain`). The raw CDG/REACHING_DEF edges are also queryable via `cypher`. See the `gitnexus-pdg-query` skill for the full query surface. + +### Shortest path between two symbols (`trace`) + +`trace` answers "how does A reach B?" in one call — the shortest directed path over `CALLS` (plus `HAS_METHOD`, so a class-rooted trace descends into its methods) instead of chaining 3–8 `context`/`impact` hops by hand. + +- `trace { from: "validateUser", to: "executeQuery" }` — shortest path between two symbols. +- Disambiguate common names with `from_uid`/`to_uid` (zero-ambiguity) or `from_file`/`to_file`; an ambiguous name returns ranked candidates. +- `maxDepth` (default 10, max 30) bounds the search; `includeTests` (default false) lets the traversal pass through test-file symbols. + +Returns ordered `hops` (each `{ name, filePath, startLine }`) and an aligned `edges[]` of `{ relType, confidence }`, so call hops and containment (`HAS_METHOD`) hops stay distinguishable. When no path exists it reports the **furthest** reachable node (where the chain breaks) and sets `truncated: true` if a traversal cap was hit first. Every result carries a `status`: `ok` / `no_path` / `ambiguous` / `not_found` / `error`. + +Cross-repo (experimental): pass `repo: "@groupName"` to trace across a group's member repos — the path may cross **one** `ContractLink` boundary (reported as a `CONTRACT_LINK` hop with the bridged contract in `crossings[]`). Omit `to` entirely to follow `from`'s outgoing HTTP call to whatever provider endpoint it lands on. Groups are configured via `group_list` / `group_sync`. + ## Resources Reference Lightweight reads (~100-500 tokens) for navigation: @@ -86,8 +127,10 @@ Lightweight reads (~100-500 tokens) for navigation: ## Graph Schema -**Nodes:** File, Function, Class, Interface, Method, Community, Process -**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS +**Nodes:** File, Folder, Function, Class, Interface, Method, CodeElement, Community, Process, Route, Tool, plus language-specific types (Struct, Enum, Trait, Impl, Namespace, Module, …) and BasicBlock (`--pdg` indexes only). The full node list lives in `gitnexus://repo/{name}/schema`. +**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, CONTAINS, MEMBER_OF, HAS_METHOD, HAS_PROPERTY, ACCESSES, METHOD_OVERRIDES, METHOD_IMPLEMENTS, STEP_IN_PROCESS, HANDLES_ROUTE, FETCHES, HANDLES_TOOL, ENTRY_POINT_OF, WRAPS, QUERIES, INJECTS, plus `--pdg`-only types (CFG, REACHING_DEF, TAINTED, SANITIZES, TAINT_PATH, CDG — zero rows on a default index). + +Read `gitnexus://repo/{name}/schema` before writing Cypher — it is the authoritative schema for the indexed repo. ```cypher MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"}) diff --git a/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md index e13c04e..2dbb71c 100644 --- a/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md +++ b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md @@ -17,7 +17,7 @@ description: "Use when the user wants to rename, extract, split, move, or restru ``` 1. impact({target: "X", direction: "upstream"}) → Map all dependents -2. query({query: "X"}) → Find execution flows involving X +2. query({search_query: "X"}) → Find execution flows involving X 3. context({name: "X"}) → See all incoming/outgoing refs 4. Plan update order: interfaces → implementations → callers → tests ``` @@ -30,7 +30,7 @@ description: "Use when the user wants to rename, extract, split, move, or restru ``` - [ ] rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits -- [ ] Review graph edits (high confidence) and ast_search edits (review carefully) +- [ ] Review graph edits (high confidence) and text_search edits (review carefully) - [ ] If satisfied: rename({..., dry_run: false}) — apply edits - [ ] detect_changes() — verify only expected files changed - [ ] Run tests for affected processes @@ -66,7 +66,7 @@ description: "Use when the user wants to rename, extract, split, move, or restru ``` rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) → 12 edits across 8 files -→ 10 graph edits (high confidence), 2 ast_search edits (review) +→ 10 graph edits (high confidence), 2 text_search edits (review) → Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}] ``` @@ -107,10 +107,10 @@ RETURN caller.name, caller.filePath ORDER BY caller.filePath ``` 1. rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) - → 12 edits: 10 graph (safe), 2 ast_search (review) + → 12 edits: 10 graph (safe), 2 text_search (review) → Files: validator.ts, login.ts, middleware.ts, config.json... -2. Review ast_search edits (config.json: dynamic reference!) +2. Review text_search edits (config.json: dynamic reference!) 3. rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false}) → Applied 12 edits across 8 files diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index f0e04e0..c429256 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -35,12 +35,12 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" - name: Install uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 with: enable-cache: true @@ -78,7 +78,7 @@ jobs: run: hatch build - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1 with: packages-dir: dist/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7f59963..c3b4716 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 65ecce3..0be9124 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -78,10 +78,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" - - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - name: Install dependencies run: uv sync --frozen --extra dev - name: Run pre-commit hooks @@ -109,11 +109,11 @@ jobs: steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - name: Install uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 with: # Avoid parallel matrix jobs racing on the same Actions cache reservation cache-suffix: py-${{ matrix.python-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cecadb9..ea285a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: fetch-depth: 0 # Required for generating release notes - name: Create Release - uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1 + uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: generate_release_notes: true draft: false diff --git a/.github/workflows/trufflehog-full-scan.yml b/.github/workflows/trufflehog-full-scan.yml index cb1bdad..ff86f8c 100644 --- a/.github/workflows/trufflehog-full-scan.yml +++ b/.github/workflows/trufflehog-full-scan.yml @@ -24,6 +24,6 @@ jobs: - name: Full Repository Scan if: inputs.scan_type == 'full-repo' - uses: trufflesecurity/trufflehog@f446421baf832d6356c42c1743d99abff52ff334 # v3.95.7 + uses: trufflesecurity/trufflehog@27b0417c16317ca9a472a9a8092acce143b49c55 # v3.95.9 with: extra_args: --results=verified,unknown diff --git a/.github/workflows/trufflehog-security.yml b/.github/workflows/trufflehog-security.yml index c98151d..2e59b5a 100644 --- a/.github/workflows/trufflehog-security.yml +++ b/.github/workflows/trufflehog-security.yml @@ -21,6 +21,6 @@ jobs: fetch-depth: 0 # Required for TruffleHog to scan git history - name: Secret Scanning - uses: trufflesecurity/trufflehog@f446421baf832d6356c42c1743d99abff52ff334 # v3.95.7 + uses: trufflesecurity/trufflehog@27b0417c16317ca9a472a9a8092acce143b49c55 # v3.95.9 with: extra_args: --results=verified,unknown diff --git a/AGENTS.md b/AGENTS.md index b0c81ae..144b059 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,7 @@ This is the canonical agent guide for the Supervaizer controller repo. Supervaiz ## Working Rules +- Use the shared [RUNWAIZE domain glossary](../glossary.md) as the canonical terminology reference. - Prefer simple, typed Python changes that match existing FastAPI/Pydantic patterns. - Do not import from inside functions, methods, or local scopes unless it is absolutely required to avoid a concrete circular import, optional dependency, or startup-cost problem. Prefer module-level imports by default, and document the reason when a local import is unavoidable. - **No guessing / no implicit fallbacks:** when protocol versions, workspace identity, action/resource contracts, authentication, or transport configuration are missing or inconsistent, fail with a clear error that names the missing configuration. Do not infer another context, broaden scope, or silently fall back. @@ -83,7 +84,7 @@ Ask. Refusing to act is always safer than taking an action that bypasses these r # GitNexus — Code Intelligence -This project is indexed by GitNexus as **supervaizer** (6281 symbols, 11689 relationships, 286 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **supervaizer** (4857 symbols, 9137 relationships, 286 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939). @@ -92,8 +93,9 @@ This project is indexed by GitNexus as **supervaizer** (6281 symbols, 11689 rela - **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. - **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "main"})`. - **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. -- When exploring unfamiliar code, use `query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When exploring unfamiliar code, use `query({search_query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `context({name: "symbolName"})`. +- For security review, `explain({target: "fileOrSymbol"})` lists taint findings (source→sink flows; needs `analyze --pdg`). ## Never Do diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fe2b5fc..fe0dc71 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- **Generic job setup contract** — New `V2JobSetupPolicy` on `V2JobPolicy.setup` lets an agent declare the actions Studio may use to preview, start, and submit a job setup (`preview_action`, `start_action`, `submit_action`, defaulting to `job.start.preview`, `job.start`, and `step.awaiting.submit`), the contexts those actions apply to (`action_scopes`), and an opaque `plan` payload. Declared actions are folded into the registration's capability action list. `V2ActionResult.setup_plan` carries the agent's plan back to Studio. `V2JobSetupPolicy` is exported at package level. Additive only; agents that omit `setup` are unaffected. + + `action_scopes` defaults to an empty list: an agent that opts in with `setup: {}` declares no scopes rather than claiming all of `workspace`, `job`, `case`, and `step`. This matches `V2JobSyncPolicy.supported_statuses` and avoids advertising context support the agent never configured. The three action ids are validated as non-blank, so a blank id is rejected at registration instead of being silently dropped from `capabilities.actions` while still appearing in the serialized `job_policy.setup`. The same validation was applied to `V2JobSyncPolicy.action`, which had the identical gap. + +### Changed + +- **Regenerated model reference and OpenAPI** — `docs/model_reference/` and `docs/api/openapi.json` were rebuilt from the current models, picking up the job setup contract along with accumulated drift since `0.20.1`. `V2JobSetupPolicy` is now declared before `V2JobPolicy` so the public reference renders the real type instead of a `ForwardRef`. + ## [1.4.0] - 2026-07-07 ### Added diff --git a/docs/api/openapi.json b/docs/api/openapi.json index fc272bb..435428b 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Supervaize API", - "description": "API version: v1 Controller version: 0.20.1\n\nAPI for controlling and managing Supervaize agents. \n\nMore information at [https://doc.supervaize.com](https://doc.supervaize.com)\n\n## Authentication\n\nSome endpoints require API key authentication. Protected endpoints expect the API key in the X-API-Key header.\n\n[Swagger](/docs)\n[Redoc](/redoc)\n[OpenAPI](/openapi.json)\n", + "description": "API version: v1 Controller version: 1.3.1\n\nAPI for controlling and managing Supervaize agents. \n\nMore information at [https://doc.supervaize.com](https://doc.supervaize.com)\n\n## Authentication\n\nSome endpoints require API key authentication. Protected endpoints expect the API key in the X-API-Key header.\n\n[Swagger](/docs)\n[Redoc](/redoc)\n[OpenAPI](/openapi.json)\n", "termsOfService": "https://supervaize.com/terms/", "contact": { "name": "Support Team", @@ -1768,17 +1768,35 @@ "summary": "A2A JSON-RPC Controller", "description": "Dispatches Supervaizer v2 controller methods over A2A JSON-RPC.", "operationId": "post_a2a_controller_a2a_post", + "parameters": [ + { + "name": "X-API-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "additionalProperties": true, "type": "object", + "additionalProperties": true, "title": "Body" } } - }, - "required": true + } }, "responses": { "200": { @@ -1786,8 +1804,8 @@ "content": { "application/json": { "schema": { - "additionalProperties": true, "type": "object", + "additionalProperties": true, "title": "Response Post A2A Controller A2A Post" } } @@ -1815,6 +1833,24 @@ "summary": "A2A SSE Event Stream", "description": "Streams Supervaizer v2 controller effects over Server-Sent Events.", "operationId": "get_a2a_events_a2a_events_get", + "parameters": [ + { + "name": "X-API-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "responses": { "200": { "description": "Successful Response", @@ -1823,6 +1859,16 @@ "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } } } @@ -3973,17 +4019,6 @@ } ], "title": "Server Agent Onboarding Status" - }, - "server_encrypted_parameters": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Server Encrypted Parameters" } }, "type": "object", @@ -4152,7 +4187,7 @@ "type": "string", "format": "date-time", "title": "Timestamp", - "default": "2026-05-15T20:25:32.352375" + "default": "2026-08-26T17:21:02.106370" }, "status_code": { "type": "integer", @@ -4317,7 +4352,7 @@ "additionalProperties": true, "type": "object", "title": "Metadata", - "description": "Agent-provided domain metadata (e.g. campaign context)" + "description": "Agent-provided domain metadata (e.g. source object context)" } }, "type": "object", diff --git a/docs/model_reference/model_core.md b/docs/model_reference/model_core.md index 218af9c..fce37b8 100644 --- a/docs/model_reference/model_core.md +++ b/docs/model_reference/model_core.md @@ -1,10 +1,6 @@ # Model Reference Core - -> **Created:** 2025-08-09 -> **Updated:** 2026-05-16 - -**Version:** 0.20.1 +**Version:** 1.3.1 ### `account.Account` @@ -114,7 +110,7 @@ _No additional fields beyond parent class._ | Field | Type | Default | Description | |---|---|---|---| | `name` | `str` | **required** | Display name of the agent | -| `id` | `str` | **required** | Unique ID generated from name | +| `id` | `str` | **required** | Stable ID derived from name via shortuuid.uuid(name=...). Renaming the agent changes this value. | | `author` | `str` | `None` | Author of the agent | | `developer` | `str` | `None` | Developer of the controller integration | | `maintainer` | `str` | `None` | Maintainer of the integration | @@ -128,13 +124,13 @@ _No additional fields beyond parent class._ | `server_agent_id` | `str` | `None` | ID assigned by server - Do not set this manually | | `server_agent_status` | `str` | `None` | Current status on server - Do not set this manually | | `server_agent_onboarding_status` | `str` | `None` | Onboarding status - Do not set this manually | -| `server_encrypted_parameters` | `str` | `None` | Encrypted parameters from server - Do not set this manually | | `max_execution_time` | `int` | 3600 | Maximum execution time in seconds, defaults to 1 hour | | `supervaize_instructions_template_path` | `str` | `None` | Optional path to a custom template file for supervaize_instructions.html page | | `instructions_path` | `str` | 'supervaize_instructions.html' | Path where the supervaize instructions page is served (relative to agent path) | | `custom_routes` | `Any` | `None` | Optional FastAPI APIRouter; mounted on the API app at /api/agents/{slug}/... | | `data_resources` | `list[data_resource.DataResource]` | — | Data resources this agent exposes for Studio CRUD access | | `supervaizer_v2_registration` | `SupervaizerV2AgentRegistrationContract` | `None` | Optional Supervaizer v2 registration contract for A2A/A2UI Studio integrations | +| `v2_methods` | `V2AgentMethods` | `None` | Optional agent-level Supervaizer v2 method declarations | ### `agent.AgentMethod` @@ -187,8 +183,8 @@ Attributes: |---|---|---|---| | `name` | `str` | **required** | The name of the method | | `method` | `str` | **required** | The name of the method in the project's codebase that will be called with the provided parameters | -| `params` | `typing.Dict[str, typing.Any]` | `None` | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | -| `fields` | `typing.List[supervaizer.agent.AgentMethodField]` | `None` | A list of field specifications for generating forms/UI, following the django.forms.fields definition | +| `params` | `dict[str, typing.Any]` | `None` | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | +| `fields` | `list[supervaizer.agent.AgentMethodField]` | `None` | A list of field specifications for generating forms/UI, following the django.forms.fields definition | | `description` | `str` | `None` | Optional description of what the method does | | `is_async` | `bool` | False | Whether the method is asynchronous | | `timeout` | `int` | 600 | Maximum automatic job duration in seconds. Use None for jobs that must run until Studio stops them manually. | @@ -316,7 +312,7 @@ ParametersSetup.from_list([ | Field | Type | Default | Description | |---|---|---|---| -| `definitions` | `Dict[str, parameter.Parameter]` | **required** | A dictionary of Parameters, where the key is the parameter name and the value is the parameter object. | +| `definitions` | `dict[str, parameter.Parameter]` | **required** | A dictionary of Parameters, where the key is the parameter name and the value is the parameter object. | ### `parameter.Parameter` @@ -403,7 +399,7 @@ public_url: full url (including scheme and port) to use for outbound connections | `environment` | `str` | **required** | Environment name (e.g., dev, staging, prod) | | `mac_addr` | `str` | **required** | MAC address to use for server identification | | `debug` | `bool` | **required** | Whether to enable debug mode | -| `agents` | `List[agent.Agent]` | **required** | List of agents to register with the server | +| `agents` | `list[agent.Agent]` | **required** | List of agents to register with the server | | `app` | `FastAPI` | **required** | FastAPI application instance | | `reload` | `bool` | **required** | Whether to enable auto-reload | | `supervisor_account` | `Account` | `None` | Account of the supervisor - can be created at supervaize.com | @@ -413,6 +409,7 @@ public_url: full url (including scheme and port) to use for outbound connections | `public_url` | `str` | `None` | Public including scheme and port to use for inbound connections | | `api_key` | `str` | `None` | Force the API key to access the supervaizer endpoints - if not provided, a random key will be generated | | `api_key_header` | `APIKeyHeader` | `None` | API key header for authentication | +| `workspace_authorization` | `V2WorkspaceAuthorizationSettings` | — | Optional Studio-signed workspace authorization verifier settings | #### Examples @@ -446,4 +443,4 @@ public_url: full url (including scheme and port) to use for outbound connections ``` -*Uploaded on 2026-05-15 20:25:31* +*Uploaded on 2026-08-26 17:21:01* diff --git a/docs/model_reference/model_extra.md b/docs/model_reference/model_extra.md index f673691..54e034c 100644 --- a/docs/model_reference/model_extra.md +++ b/docs/model_reference/model_extra.md @@ -1,10 +1,6 @@ # Model Reference extra - -> **Created:** 2025-08-08 -> **Updated:** 2026-05-16 - -**Version:** 0.20.1 +**Version:** 1.3.1 ### `common.SvBaseModel` @@ -47,7 +43,7 @@ Base model for agent job context parameters | Field | Type | Default | Description | |---|---|---|---| | `job_context` | `JobContext` | **required** | | -| `job_fields` | `Dict[str, Any]` | **required** | | +| `job_fields` | `dict[str, Any]` | **required** | | ### `agent.AgentMethodParams` @@ -55,7 +51,7 @@ Method parameters for agent operations. | Field | Type | Default | Description | |---|---|---|---| -| `params` | `Dict[str, Any]` | — | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | +| `params` | `dict[str, Any]` | — | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | ### `agent.AgentMethods` @@ -94,11 +90,10 @@ Response model for agent endpoints - values provided by Agent.registration_info | `description` | `str` | **required** | | | `tags` | `list[str]` | `None` | | | `methods` | `AgentMethods` | `None` | | -| `parameters_setup` | `typing.List[typing.Dict[str, typing.Any]]` | `None` | | +| `parameters_setup` | `list[dict[str, typing.Any]]` | `None` | | | `server_agent_id` | `str` | `None` | | | `server_agent_status` | `str` | `None` | | | `server_agent_onboarding_status` | `str` | `None` | | -| `server_encrypted_parameters` | `str` | `None` | | ### `case.CaseNodes` @@ -108,7 +103,7 @@ Response model for agent endpoints - values provided by Agent.registration_info | Field | Type | Default | Description | |---|---|---|---| -| `nodes` | `List[case.CaseNode]` | [] | | +| `nodes` | `list[case.CaseNode]` | [] | | ### `data_resource.DataResource` @@ -145,12 +140,14 @@ Example:: | `fields` | `list[data_resource.DataResourceField]` | — | | | `read_only` | `bool` | False | | | `importable` | `bool` | False | Enables CSV bulk import route | -| `on_list` | `typing.Callable[..., list[dict[str, typing.Any]]]` | `None` | | -| `on_get` | `typing.Callable[..., dict[str, typing.Any] | None]` | `None` | | -| `on_create` | `typing.Callable[..., dict[str, typing.Any]]` | `None` | | -| `on_update` | `typing.Callable[..., dict[str, typing.Any] | None]` | `None` | | -| `on_delete` | `typing.Callable[..., bool]` | `None` | | -| `on_import` | `typing.Callable[..., dict[str, typing.Any]]` | `None` | | +| `scope` | `Literal['workspace', 'mission', 'job']` | 'workspace' | Studio context boundary for this resource. | +| `requires_context` | `list[str]` | — | Context keys Studio must send for resource access control. | +| `on_list` | `collections.abc.Callable[..., list[dict[str, typing.Any]]]` | `None` | | +| `on_get` | `collections.abc.Callable[..., dict[str, typing.Any] | None]` | `None` | | +| `on_create` | `collections.abc.Callable[..., dict[str, typing.Any]]` | `None` | | +| `on_update` | `collections.abc.Callable[..., dict[str, typing.Any] | None]` | `None` | | +| `on_delete` | `collections.abc.Callable[..., bool]` | `None` | | +| `on_import` | `collections.abc.Callable[..., dict[str, typing.Any]]` | `None` | | ### `job.Job` @@ -226,6 +223,56 @@ _No additional fields beyond parent class._ | `job_policy` | `V2JobPolicy` | — | | | `resources` | `list[contracts.V2ResourceDefinition]` | — | | | `datasets` | `list[contracts.V2DatasetDefinition]` | — | | +| `dashboards` | `list[contracts.V2DashboardDefinition]` | — | | +| `workspace_binding` | `V2WorkspaceBindingDefinition` | `None` | | + +### `contracts.V2ActionRequest` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `request_id` | `str` | **required** | | +| `actor` | `V2ActorContext` | **required** | | +| `workspace` | `V2WorkspaceContext` | **required** | | +| `mission_id` | `str` | **required** | | +| `agent_slug` | `str` | **required** | | +| `surface` | `str` | **required** | | +| `action` | `str` | **required** | | +| `input` | `dict[str, Any]` | — | | +| `idempotency_key` | `str` | `None` | | +| `draft_session_id` | `str` | `None` | | +| `job_id` | `str` | `None` | | +| `case_id` | `str` | `None` | | +| `step_id` | `str` | `None` | | +| `workspace_authorization` | `V2VerifiedWorkspaceContext` | `None` | | + +### `contracts.V2AgentMethod` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `method` | `str` | **required** | | +| `params` | `dict[str, Any]` | — | | +| `description` | `str` | `None` | | +| `is_async` | `bool` | False | | +| `timeout` | `int` | 600 | | + +### `contracts.V2AgentMethods` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `refresh` | `V2AgentMethod` | `None` | | +| `custom` | `dict[str, contracts.V2AgentMethod]` | — | | ### `case.Case` @@ -247,11 +294,11 @@ _No additional fields beyond parent class._ | `account` | `ForwardRef('Account')` | **required** | | | `description` | `str` | **required** | | | `status` | `` | **required** | | -| `updates` | `List[case.CaseNodeUpdate]` | [] | | +| `updates` | `list[case.CaseNodeUpdate]` | [] | | | `total_cost` | `float` | 0.0 | | -| `final_delivery` | `typing.Dict[str, typing.Any]` | `None` | | +| `final_delivery` | `dict[str, typing.Any]` | `None` | | | `finished_at` | `datetime` | `None` | | -| `metadata` | `Dict[str, Any]` | — | Agent-provided domain metadata (e.g. contact context) | +| `metadata` | `dict[str, Any]` | — | Agent-provided domain metadata (e.g. contact context) | ### `case.CaseNode` @@ -284,15 +331,75 @@ Returns: | `index` | `int` | `None` | | | `cost` | `float` | `None` | | | `name` | `str` | `None` | | -| `payload` | `typing.Dict[str, typing.Any]` | `None` | | +| `payload` | `dict[str, typing.Any]` | `None` | | | `is_final` | `bool` | False | | | `upsert` | `bool` | False | | | `error` | `str` | `None` | | | `scheduled_at` | `datetime` | `None` | | | `scheduled_method` | `str` | `None` | | -| `scheduled_params` | `typing.Dict[str, typing.Any]` | `None` | | +| `scheduled_params` | `dict[str, typing.Any]` | `None` | | | `scheduled_status` | `str` | `None` | | +### `context.ContextCitation` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `title` | `str` | **required** | | +| `source_type` | `str` | **required** | | +| `version` | `int` | **required** | | + +### `context.ContextOpenResponse` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `title` | `str` | **required** | | +| `scope` | `Literal['workspace', 'mission']` | **required** | | +| `source_type` | `str` | **required** | | +| `version` | `int` | **required** | | +| `instructions` | `str` | '' | | +| `tags` | `list[str]` | — | | +| `content` | `str` | **required** | | +| `citations` | `list[context.ContextCitation]` | — | | + +### `context.ContextSearchResponse` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `query` | `str` | **required** | | +| `results` | `list[context.ContextSearchResult]` | — | | + +### `context.ContextSearchResult` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `title` | `str` | **required** | | +| `scope` | `Literal['workspace', 'mission']` | **required** | | +| `source_type` | `str` | **required** | | +| `version` | `int` | **required** | | +| `tags` | `list[str]` | — | | +| `excerpt` | `str` | '' | | +| `score` | `int` | 0 | | +| `citation` | `ContextCitation` | `None` | | + ### `contracts.AgentMethodContract` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -435,6 +542,8 @@ Canonical controller surface advertised by a Supervaizer server. | `read_only` | `bool` | False | | | `importable` | `bool` | False | | | `operations` | `dict[str, bool]` | — | | +| `scope` | `Literal['workspace', 'mission', 'job']` | 'workspace' | | +| `requires_context` | `list[str]` | — | | ### `contracts.DataResourceFieldContract` @@ -493,6 +602,7 @@ Minimal schema for server.register details. | `url` | `str` | **required** | | | `uri` | `str` | **required** | | | `api_version` | `str` | **required** | | +| `controller_version` | `str` | `None` | | | `environment` | `str` | `None` | | | `agents` | `list[contracts.AgentRegistrationContract]` | — | | @@ -532,7 +642,40 @@ Minimal schema for server.register details. | `sse` | `bool` | True | | | `push_notifications` | `bool` | False | | -### `contracts.V2ActionRequest` +### `contracts.V2A2UIResourceImportColumn` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `id` | `str` | **required** | | +| `label` | `str` | `None` | | +| `type` | `str` | 'string' | | +| `required` | `bool` | False | | + +### `contracts.V2A2UIResourceImportDocument` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +A2UI-shaped resource import surface consumed by Studio. + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `type` | `Literal['ResourceImport']` | 'ResourceImport' | | +| `id` | `str` | **required** | | +| `title` | `str` | **required** | | +| `resource` | `str` | **required** | | +| `accepted_formats` | `list[Literal['csv', 'xlsx']]` | — | | +| `fields` | `list[contracts.V2ResourceFieldDefinition]` | — | | +| `columns` | `list[contracts.V2A2UIResourceImportColumn]` | — | | +| `submit` | `V2A2UISubmitDefinition` | **required** | | +| `state` | `dict[str, Any]` | — | | + +### `contracts.V2A2UISubmitDefinition` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -540,19 +683,8 @@ Minimal schema for server.register details. | Field | Type | Default | Description | |---|---|---|---| -| `request_id` | `str` | **required** | | -| `actor` | `V2ActorContext` | **required** | | -| `workspace` | `V2WorkspaceContext` | **required** | | -| `mission_id` | `str` | **required** | | -| `agent_slug` | `str` | **required** | | -| `surface` | `str` | **required** | | | `action` | `str` | **required** | | -| `input` | `dict[str, Any]` | — | | -| `idempotency_key` | `str` | `None` | | -| `draft_session_id` | `str` | `None` | | -| `job_id` | `str` | `None` | | -| `case_id` | `str` | `None` | | -| `step_id` | `str` | `None` | | +| `label` | `str` | `None` | | ### `contracts.V2ActionResult` @@ -564,6 +696,9 @@ Minimal schema for server.register details. |---|---|---|---| | `status` | `Literal['ok', 'error']` | **required** | | | `effects` | `list[contracts.V2Effect]` | — | | +| `job_state` | `ForwardRef('V2JobStateSnapshot | None')` | `None` | | +| `replay_safety` | `V2ReplaySafetyMetadata` | `None` | | +| `setup_plan` | `dict[str, typing.Any]` | `None` | | ### `contracts.V2ActorContext` @@ -677,8 +812,87 @@ Minimal schema for server.register details. | `title` | `str` | `None` | | | `status` | `str` | `None` | | | `external_id` | `str` | `None` | | +| `metadata` | `dict[str, Any]` | — | | | `steps` | `list[contracts.V2StepSnapshot]` | — | | +### `contracts.V2ContextAssignment` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `items` | `list[contracts.V2ContextAssignmentItem]` | **required** | | +| `mission_id` | `str` | `None` | | +| `job_id` | `str` | **required** | | +| `assigned_at` | `str` | **required** | | + +### `contracts.V2ContextAssignmentItem` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `version` | `int` | **required** | | +| `scope` | `Literal['workspace', 'mission']` | **required** | | +| `title` | `str` | **required** | | + +### `contracts.V2DashboardDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `id` | `str` | **required** | | +| `label` | `str` | **required** | | +| `surface` | `str` | 'mission.analytics' | | +| `widgets` | `list[contracts.V2DashboardWidgetDefinition]` | — | | + +### `contracts.V2DashboardWidgetDataRef` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `mode` | `Literal['ref', 'action', 'inline']` | 'ref' | | +| `datasetId` | `str` | `None` | | +| `action` | `str` | `None` | | +| `input` | `dict[str, Any]` | — | | +| `values` | `list[dict[str, typing.Any]]` \| `dict[str, typing.Any]` | `None` | | + +### `contracts.V2DashboardWidgetDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `id` | `str` | **required** | | +| `title` | `str` | **required** | | +| `data` | `V2DashboardWidgetDataRef` | `None` | | +| `layout` | `dict[str, Any]` | — | | +| `visualization` | `V2DashboardWidgetVisualization` | — | | + +### `contracts.V2DashboardWidgetVisualization` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `type` | `Literal['table', 'metric', 'vega-lite', 'custom']` | 'table' | | +| `spec` | `dict[str, typing.Any]` | `None` | | + ### `contracts.V2DatasetDefinition` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -690,6 +904,7 @@ Minimal schema for server.register details. | `id` | `str` | **required** | | | `label` | `str` | **required** | | | `auto_surface` | `bool` | False | | +| `display` | `V2ResourceDisplayDefinition` | `None` | | ### `contracts.V2Effect` @@ -700,6 +915,23 @@ Minimal schema for server.register details. | Field | Type | Default | Description | |---|---|---|---| | `type` | `str` | **required** | | +| `job_id` | `str` | `None` | | +| `case_id` | `str` | `None` | | +| `step_id` | `str` | `None` | | +| `resource` | `str` | `None` | | +| `dataset` | `str` | `None` | | +| `artifact_id` | `str` | `None` | | +| `status` | `str` | `None` | | +| `message` | `str` | `None` | | +| `count` | `int` | `None` | | +| `item` | `dict[str, typing.Any]` | `None` | | +| `items` | `list[dict[str, typing.Any]]` | `None` | | +| `rows` | `list[dict[str, typing.Any]]` | `None` | | +| `errors` | `list[dict[str, typing.Any]]` | `None` | | +| `gaps` | `list[dict[str, typing.Any]]` | `None` | | +| `summary` | `dict[str, typing.Any]` | `None` | | +| `case` | `dict[str, typing.Any]` | `None` | | +| `data` | `dict[str, typing.Any]` | `None` | | ### `contracts.V2JobPolicy` @@ -713,6 +945,23 @@ Minimal schema for server.register details. | `offline_start_policy` | `Literal['block']` | 'block' | | | `offline_running_policy` | `Literal['fail_in_studio']` | 'fail_in_studio' | | | `sync` | `V2JobSyncPolicy` | `None` | | +| `setup` | `V2JobSetupPolicy` | `None` | | + +### `contracts.V2JobSetupPolicy` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +Generic agent-declared job setup actions. + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `preview_action` | `str` | 'job.start.preview' | | +| `start_action` | `str` | 'job.start' | | +| `submit_action` | `str` | 'step.awaiting.submit' | | +| `action_scopes` | `list[Literal['workspace', 'job', 'case', 'step']]` | — | | +| `plan` | `dict[str, typing.Any]` | `None` | | ### `contracts.V2JobSnapshot` @@ -739,7 +988,7 @@ Minimal schema for server.register details. | `type` | `Literal['fresh_start', 'external']` | **required** | | | `external_ref` | `str` | `None` | | | `previous_job_id` | `str` | `None` | | -| `target_type` | `str` | `None` | | +| `target_type` | `str` | `None` | Agent-declared business object type for external sources (e.g. project). Open vocabulary unlike protocol-fixed fields such as step activity. | ### `contracts.V2JobStateSnapshot` @@ -775,18 +1024,19 @@ Minimal schema for server.register details. | `external_version` | `str` | `None` | | | `sync_cursor` | `str` | `None` | | | `observed_at` | `str` | `None` | | -| `job_state` | `V2JobStateSnapshot` | `None` | | ### `contracts.V2MountedResourceViewDefinition` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) +Agent override that mounts an A2UI surface on a full resource view. + #### Model Fields | Field | Type | Default | Description | |---|---|---|---| -| `view` | `str` | **required** | | -| `surface` | `str` | **required** | | +| `view` | `str` | **required** | Generated resource view replaced by the mount (e.g. import, edit). | +| `surface` | `str` | **required** | Registered A2UI surface id served for this resource view. | ### `contracts.V2ProtocolVersions` @@ -825,6 +1075,8 @@ Minimal schema for server.register details. | `id` | `str` | **required** | | | `label` | `str` | **required** | | | `auto_surface` | `bool` | False | | +| `scope` | `Literal['workspace', 'mission', 'job']` | 'workspace' | | +| `requires_context` | `list[str]` | — | | | `operations` | `list[str]` | — | | | `display` | `V2ResourceDisplayDefinition` | `None` | | | `fields` | `list[contracts.V2ResourceFieldDefinition]` | — | | @@ -906,6 +1158,7 @@ Minimal schema for server.register details. | `job_id` | `str` | `None` | | | `case_id` | `str` | `None` | | | `step_id` | `str` | `None` | | +| `workspace_authorization` | `V2VerifiedWorkspaceContext` | `None` | | ### `contracts.V2SurfaceResult` @@ -920,6 +1173,78 @@ Minimal schema for server.register details. | `a2ui_catalog_version` | `str` | `None` | | | `document` | `dict[str, Any]` | — | | +### `contracts.V2VerifiedWorkspaceContext` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `grant_id` | `str` | **required** | | +| `workspace_id` | `str` | **required** | | +| `workspace_slug` | `str` | `None` | | +| `agent_id` | `str` | **required** | | +| `agent_slug` | `str` | **required** | | +| `server_id` | `str` | **required** | | +| `scopes` | `list[str]` | — | | +| `agent_workspace_ref` | `str` | `None` | | + +### `contracts.V2WorkspaceAuthorizationSettings` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `enabled` | `bool` | False | | +| `issuer` | `str` | `None` | | +| `audience` | `str` | `None` | | +| `public_key_pem` | `str` | `None` | | +| `jwks_url` | `str` | `None` | | +| `leeway_seconds` | `int` | 30 | | + +### `contracts.V2WorkspaceBindingCreateDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `surface` | `str` | 'workspace_binding.create' | | +| `action` | `str` | 'workspace_binding.create' | | +| `fields` | `list[contracts.V2ResourceFieldDefinition]` | — | | + +### `contracts.V2WorkspaceBindingDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `required` | `bool` | False | | +| `modes` | `list[Literal['bind_existing', 'create_and_bind']]` | — | | +| `reference_label` | `str` | 'Agent workspace reference' | | +| `reference_help` | `str` | 'Select or create the agent-side record this Studio workspace may access.' | | +| `reference_placeholder` | `str` | 'Example: workspace-prod' | | +| `existing` | `V2WorkspaceBindingExistingDefinition` | `None` | | +| `create` | `V2WorkspaceBindingCreateDefinition` | `None` | | + +### `contracts.V2WorkspaceBindingExistingDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `action` | `str` | 'workspace_binding.options' | | +| `value_field` | `str` | 'agent_workspace_ref' | | +| `label_field` | `str` | 'display_name' | | + ### `contracts.V2WorkspaceContext` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -946,6 +1271,7 @@ Studio request context passed to DataResource callbacks. | `mission_id` | `str` | `None` | | | `agent_slug` | `str` | **required** | | | `request_id` | `str` | `None` | | +| `workspace_authorization` | `V2VerifiedWorkspaceContext` | `None` | | ### `data_resource.DataResourceField` @@ -978,7 +1304,7 @@ Deployment plan containing all actions to be taken. | `environment` | `str` | **required** | | | `region` | `str` | **required** | | | `project_id` | `str` | `None` | | -| `actions` | `List[deploy.drivers.base.ResourceAction]` | [] | | +| `actions` | `list[deploy.drivers.base.ResourceAction]` | [] | | | `total_cost_estimate` | `str` | `None` | | | `estimated_duration` | `str` | `None` | | | `current_image` | `str` | `None` | | @@ -986,8 +1312,8 @@ Deployment plan containing all actions to be taken. | `current_status` | `str` | `None` | | | `target_image` | `str` | **required** | | | `target_port` | `int` | 8000 | | -| `target_env_vars` | `Dict[str, str]` | {} | | -| `target_secrets` | `Dict[str, str]` | {} | | +| `target_env_vars` | `dict[str, str]` | {} | | +| `target_secrets` | `dict[str, str]` | {} | | ### `deploy.drivers.base.DeploymentResult` @@ -1004,7 +1330,7 @@ Result of a deployment operation. | `health_status` | `str` | 'unknown' | | | `deployment_time` | `float` | `None` | | | `error_message` | `str` | `None` | | -| `error_details` | `typing.Dict[str, typing.Any]` | `None` | | +| `error_details` | `dict[str, typing.Any]` | `None` | | ### `deploy.state.DeploymentState` @@ -1029,7 +1355,7 @@ Deployment state model. | `port` | `int` | 8000 | Application port | | `api_key_generated` | `bool` | False | Whether API key was generated | | `rsa_key_generated` | `bool` | False | Whether RSA key was generated | -| `provider_data` | `Dict[str, Any]` | — | Platform-specific data | +| `provider_data` | `dict[str, Any]` | — | Platform-specific data | ### `deploy.drivers.base.ResourceAction` @@ -1042,7 +1368,7 @@ Represents an action to be taken on a resource. | `resource_name` | `str` | **required** | | | `description` | `str` | **required** | | | `cost_estimate` | `str` | `None` | | -| `metadata` | `typing.Dict[str, typing.Any]` | `None` | | +| `metadata` | `dict[str, typing.Any]` | `None` | | ### `deploy.health.HealthCheckConfig` @@ -1056,7 +1382,7 @@ Configuration for health check operations. | `max_delay` | `float` | 30.0 | | | `backoff_multiplier` | `float` | 2.0 | | | `success_threshold` | `int` | 1 | | -| `endpoints` | `typing.List[str]` | `None` | | +| `endpoints` | `list[str]` | `None` | | ### `deploy.health.HealthCheckResult` @@ -1079,11 +1405,11 @@ Result of a health check operation. | Field | Type | Default | Description | |---|---|---|---| -| `source` | `Dict[str, Any]` | **required** | | +| `source` | `dict[str, Any]` | **required** | | | `account` | `Any` | **required** | | | `type` | `` | **required** | | | `object_type` | `str` | **required** | | -| `details` | `Dict[str, Any]` | **required** | | +| `details` | `dict[str, Any]` | **required** | | ### `event.AgentRegisterEvent` @@ -1159,7 +1485,7 @@ _No additional fields beyond parent class._ | `created_at` | `datetime` | `None` | | | `agent_parameters` | `list[dict[str, typing.Any]]` | `None` | | | `case_ids` | `list[str]` | [] | | -| `metadata` | `dict[str, Any]` | — | Agent-provided domain metadata (e.g. campaign context) | +| `metadata` | `dict[str, Any]` | — | Agent-provided domain metadata (e.g. source object context) | ### `job.JobInstructions` @@ -1236,7 +1562,7 @@ Standard error response model | `error` | `str` | **required** | | | `error_type` | `` | **required** | | | `detail` | `str` | `None` | | -| `timestamp` | `datetime` | datetime.datetime(2026, 5, 15, 20, 25, 31, 892520) | | +| `timestamp` | `datetime` | datetime.datetime(2026, 8, 26, 17, 21, 1, 586513) | | | `status_code` | `int` | **required** | | ### `routes.RegistrationRefreshRequest` @@ -1252,7 +1578,7 @@ Request model for re-sending the server registration event. | `reason` | `str` | `None` | | | `requested_at` | `str` | `None` | | -### `server.ServerInfo` +### `server_info.ServerInfo` Complete server information for storage. @@ -1263,7 +1589,7 @@ Complete server information for storage. | `port` | `int` | **required** | | | `api_version` | `str` | **required** | | | `environment` | `str` | **required** | | -| `agents` | `List[Dict[str, str]]` | **required** | | +| `agents` | `list[dict[str, str]]` | **required** | | | `start_time` | `float` | **required** | | | `created_at` | `str` | **required** | | | `updated_at` | `str` | **required** | | @@ -1278,7 +1604,30 @@ A base class for creating Pydantic models. | `type` | `` | **required** | | | `category` | `` | **required** | | | `severity` | `` | **required** | | -| `details` | `Dict[str, Any]` | **required** | | +| `details` | `dict[str, Any]` | **required** | | + +### `workspace_authorization.WorkspaceAuthorizationClaims` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `iss` | `str` | **required** | | +| `aud` | `str` \| `list[str]` | **required** | | +| `sub` | `str` | `None` | | +| `grant_id` | `str` | **required** | | +| `workspace_id` | `str` | **required** | | +| `workspace_slug` | `str` | `None` | | +| `agent_id` | `str` | **required** | | +| `agent_slug` | `str` | **required** | | +| `server_id` | `str` | **required** | | +| `scopes` | `list[str]` | — | | +| `agent_workspace_ref` | `str` | `None` | | +| `iat` | `int` | `None` | | +| `exp` | `int` | **required** | | +| `jti` | `str` | `None` | | -*Uploaded on 2026-05-15 20:25:31* +*Uploaded on 2026-08-26 17:21:01* diff --git a/src/supervaizer/__init__.py b/src/supervaizer/__init__.py index 115f12b..6177720 100644 --- a/src/supervaizer/__init__.py +++ b/src/supervaizer/__init__.py @@ -175,6 +175,7 @@ "V2DatasetDefinition": ("supervaizer.contracts", "V2DatasetDefinition"), "V2Effect": ("supervaizer.contracts", "V2Effect"), "V2JobPolicy": ("supervaizer.contracts", "V2JobPolicy"), + "V2JobSetupPolicy": ("supervaizer.contracts", "V2JobSetupPolicy"), "V2JobSyncPolicy": ("supervaizer.contracts", "V2JobSyncPolicy"), "V2JobSnapshot": ("supervaizer.contracts", "V2JobSnapshot"), "V2JobSource": ("supervaizer.contracts", "V2JobSource"), diff --git a/src/supervaizer/contracts.py b/src/supervaizer/contracts.py index 08827a5..545ea3b 100644 --- a/src/supervaizer/contracts.py +++ b/src/supervaizer/contracts.py @@ -360,12 +360,39 @@ class V2JobSyncPolicy(ContractModel): action: str = "job.sync" supported_statuses: list[str] = Field(default_factory=list) + @field_validator("action") + @classmethod + def validate_action_is_named(cls, value: str) -> str: + if not value.strip(): + raise ValueError("job sync policy action must be a non-empty action id") + return value + + +class V2JobSetupPolicy(ContractModel): + """Generic agent-declared job setup actions.""" + + preview_action: str = "job.start.preview" + start_action: str = "job.start" + submit_action: str = "step.awaiting.submit" + action_scopes: list[Literal["workspace", "job", "case", "step"]] = Field( + default_factory=list + ) + plan: dict[str, Any] | None = None + + @field_validator("preview_action", "start_action", "submit_action") + @classmethod + def validate_action_is_named(cls, value: str) -> str: + if not value.strip(): + raise ValueError("job setup policy actions must be non-empty action ids") + return value + class V2JobPolicy(ContractModel): default_timeout_seconds: int | None = None offline_start_policy: Literal["block"] = "block" offline_running_policy: Literal["fail_in_studio"] = "fail_in_studio" sync: V2JobSyncPolicy | None = None + setup: V2JobSetupPolicy | None = None class V2ResourceDisplayDefinition(ContractModel): @@ -586,7 +613,7 @@ def build_v2_agent_registration( dashboard_definitions = _contract_list(dashboards, V2DashboardDefinition) workspace_binding_definition = _workspace_binding(workspace_binding) agent_method_definitions = _agent_methods(agent_methods) - sync_policy = _job_policy(job_policy) + job_policy_definition = _job_policy(job_policy) capability_surfaces = _unique_strings([ *surfaces, @@ -599,7 +626,8 @@ def build_v2_agent_registration( *actions, *_resource_action_ids(resource_definitions), *_dataset_action_ids(dataset_definitions), - *(_job_sync_actions(sync_policy)), + *_job_sync_actions(job_policy_definition), + *_job_setup_actions(job_policy_definition), *_workspace_binding_action_ids(workspace_binding_definition), *_agent_method_action_ids(agent_method_definitions), ]) @@ -631,7 +659,7 @@ def build_v2_agent_registration( case_lanes=_contract_list(case_lanes, V2CaseLaneDefinition), artifact_types=_contract_list(artifact_types, V2ArtifactTypeDefinition), ), - job_policy=sync_policy, + job_policy=job_policy_definition, resources=resource_definitions, datasets=dataset_definitions, dashboards=dashboard_definitions, @@ -731,6 +759,16 @@ def _job_sync_actions(job_policy: V2JobPolicy) -> list[str]: return [job_policy.sync.action] +def _job_setup_actions(job_policy: V2JobPolicy) -> list[str]: + if job_policy.setup is None: + return [] + return [ + job_policy.setup.preview_action, + job_policy.setup.start_action, + job_policy.setup.submit_action, + ] + + def _agent_method_action_ids(agent_methods: V2AgentMethods | None) -> list[str]: if agent_methods is None: return [] @@ -880,6 +918,7 @@ class V2ActionResult(ContractModel): effects: list[V2Effect] = Field(default_factory=list) job_state: V2JobStateSnapshot | None = None replay_safety: V2ReplaySafetyMetadata | None = None + setup_plan: dict[str, Any] | None = None class V2SurfaceResult(ContractModel): diff --git a/tests/test_a2a.py b/tests/test_a2a.py index 9127859..36f2908 100644 --- a/tests/test_a2a.py +++ b/tests/test_a2a.py @@ -464,9 +464,14 @@ def test_a2a_controller_dispatches_registered_v2_action( def start_job(request: V2ActionRequest) -> V2ActionResult: assert request.action == "job.start" + assert request.mission_id == "mission-1" + assert request.job_id == "job-123" + assert request.case_id == "case-123" + assert request.step_id == "step-123" return V2ActionResult( status="ok", effects=[V2Effect(type="job.created", job_id="job-123")], + setup_plan={"agent_descriptor": {"campaign_template_id": "opaque-id"}}, ) register_v2_action_handler(server_fixture, "job.start", start_job) @@ -479,7 +484,8 @@ def start_job(request: V2ActionRequest) -> V2ActionResult: "jsonrpc": "2.0", "id": "rpc-3", "method": SUPERVAIZER_ACTION_INVOKE_METHOD, - "params": _v2_action_payload(action="job.start", agent_slug=agent.slug), + "params": _v2_action_payload(action="job.start", agent_slug=agent.slug) + | {"job_id": "job-123", "case_id": "case-123", "step_id": "step-123"}, }, ) @@ -490,6 +496,9 @@ def start_job(request: V2ActionRequest) -> V2ActionResult: assert payload["result"]["effects"] == [ {"type": "job.created", "job_id": "job-123"} ] + assert payload["result"]["setup_plan"] == { + "agent_descriptor": {"campaign_template_id": "opaque-id"} + } def test_a2a_workspace_authorization_missing_token_blocks_action_handler( diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 844321b..1a4fd53 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -38,7 +38,9 @@ V2DashboardWidgetDefinition, V2DashboardWidgetVisualization, V2Effect, + V2JobSetupPolicy, V2JobStateSnapshot, + V2JobSyncPolicy, V2JobSyncResult, V2ReplaySafetyMetadata, V2ResourceFieldDefinition, @@ -545,6 +547,60 @@ def test_v2_action_request_and_result_fixture() -> None: ] +def test_v2_job_setup_policy_round_trips_through_registration_builder() -> None: + registration = build_v2_agent_registration( + agent_id="agent-1", + agent_slug="agent-1", + display_name="Agent 1", + agent_card_url="/.well-known/agent.json", + controller_url="/a2a", + a2ui_catalog_version="test.0", + job_policy={ + "setup": { + "preview_action": "job.start.preview", + "start_action": "job.start", + "submit_action": "step.awaiting.submit", + "action_scopes": ["workspace", "job", "case", "step"], + } + }, + ) + payload = registration.model_dump(mode="json") + round_trip = SupervaizerV2AgentRegistrationContract.model_validate(payload) + + assert "setup" not in payload["capabilities"] + assert round_trip.job_policy.setup is not None + assert round_trip.job_policy.setup.action_scopes == [ + "workspace", + "job", + "case", + "step", + ] + assert set(round_trip.capabilities.actions) >= { + "job.start.preview", + "job.start", + "step.awaiting.submit", + } + + +def test_v2_job_setup_policy_declares_no_scopes_by_default() -> None: + policy = V2JobSetupPolicy() + + assert policy.action_scopes == [] + + +def test_v2_job_setup_policy_rejects_blank_action_ids() -> None: + for field in ("preview_action", "start_action", "submit_action"): + with pytest.raises(ValidationError): + V2JobSetupPolicy.model_validate({field: ""}) + with pytest.raises(ValidationError): + V2JobSetupPolicy.model_validate({field: " "}) + + +def test_v2_job_sync_policy_rejects_blank_action_id() -> None: + with pytest.raises(ValidationError): + V2JobSyncPolicy.model_validate({"action": ""}) + + def test_v2_action_result_validates_replay_safety() -> None: with pytest.raises(ValidationError): V2ActionResult.model_validate({ @@ -553,6 +609,19 @@ def test_v2_action_result_validates_replay_safety() -> None: }) +def test_v2_action_result_requires_setup_plan_mapping() -> None: + result = V2ActionResult.model_validate({ + "status": "ok", + "setup_plan": {"agent_descriptor": {"campaign_template_id": "opaque-id"}}, + }) + + assert result.setup_plan == { + "agent_descriptor": {"campaign_template_id": "opaque-id"} + } + with pytest.raises(ValidationError): + V2ActionResult.model_validate({"status": "ok", "setup_plan": ["invalid"]}) + + def test_v2_surface_request_and_result_models() -> None: request = V2SurfaceRequest.model_validate({ "request_id": "surface-request-1", diff --git a/tests/test_workbench_routes.py b/tests/test_workbench_routes.py index 8d45eba..63daaaa 100644 --- a/tests/test_workbench_routes.py +++ b/tests/test_workbench_routes.py @@ -4,12 +4,6 @@ # If a copy of the MPL was not distributed with this file, you can obtain one at # https://mozilla.org/MPL/2.0/. -# Copyright (c) 2024-2026 Alain Prasquier - Supervaize.com. All rights reserved. -# -# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -# If a copy of the MPL was not distributed with this file, you can obtain one at -# https://mozilla.org/MPL/2.0/. - """Tests for workbench routes module.""" from datetime import UTC, datetime diff --git a/uv.lock b/uv.lock index ca24f93..7da1fbe 100644 --- a/uv.lock +++ b/uv.lock @@ -406,52 +406,52 @@ wheels = [ [[package]] name = "cryptography" -version = "49.0.0" +version = "50.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, + { url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" }, + { url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" }, + { url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" }, + { url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" }, + { url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" }, + { url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" }, + { url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" }, + { url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" }, + { url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, + { url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, ] [[package]] @@ -1344,11 +1344,11 @@ wheels = [ [[package]] name = "pyasn1" -version = "0.6.3" +version = "0.6.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" }, ] [[package]]