Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4bb3831
refactor: remove _group_records_by_agent wrapper
Aureliolo May 3, 2026
712617e
refactor: consolidate _deduplicate_tags into memory.utils
Aureliolo May 3, 2026
59fc7ed
refactor: add core normalisation helpers and migrate URL/path call sites
Aureliolo May 3, 2026
7ce2b31
refactor: consolidate org-chart getNodeLabel + budget dimension switch
Aureliolo May 3, 2026
6dafa16
fix: switch DigiCert + Sectigo TSA defaults to https
Aureliolo May 3, 2026
0ad7894
fix: route SSRF audit events to security audit chain
Aureliolo May 3, 2026
0a4f2dc
feat: rate-limit /oauth/initiate and /settings/security/import
Aureliolo May 3, 2026
1064874
refactor: extract REJECTION_REASON_REQUIRED to shared module
Aureliolo May 3, 2026
5a227fd
fix: close tar.extractfile handles in backup service + retention
Aureliolo May 3, 2026
c66d2be
refactor: extract scoring magic numbers to module-level constants
Aureliolo May 3, 2026
3b06098
feat: bound Prometheus tool_name label via snapshot from ToolRegistry
Aureliolo May 3, 2026
f9dbcf3
fix: serialise circuit_breaker / trust / inmemory mutations under locks
Aureliolo May 3, 2026
86c1f48
feat: install lifecycle locks on Worker + ApprovalTimeoutScheduler
Aureliolo May 3, 2026
833f9a5
docs(cli): fill Long + Example on cobra commands
Aureliolo May 3, 2026
acac9ec
fix: enforce JSON validity on SQLite TEXT columns mirroring Postgres …
Aureliolo May 3, 2026
46b4f08
perf: batch save_many in approval expiry loop
Aureliolo May 3, 2026
a077f87
feat: wire ApprovalTimeoutScheduler at app startup with WaitForeverPo…
Aureliolo May 3, 2026
07c3781
feat: inject Clock seam into 14 time-reading sites
Aureliolo May 3, 2026
1d3ebec
feat: add extra=forbid to 489 ConfigDicts across listed domains
Aureliolo May 3, 2026
81545f9
feat: make limit required on persistence list methods
Aureliolo May 3, 2026
7d470b7
test: extend approval conformance with save_many dual-backend coverage
Aureliolo May 3, 2026
c04d943
fix: align persistence protocols and remove unreachable limit branches
Aureliolo May 3, 2026
00f456c
fix: address pre-PR review findings on the audit-bucket PR
Aureliolo May 3, 2026
966cb20
fix: babysit round 2, address CI failures and CodeRabbit/Gemini findings
Aureliolo May 3, 2026
e5e88d2
test: drop security.timeout_check_interval_seconds from ghost-wired b…
Aureliolo May 3, 2026
0d16924
fix: babysit round 4, address CodeRabbit findings on the new head
Aureliolo May 3, 2026
ee674e3
fix: drop unused type-ignore on lock-restore in TOCTOU regression test
Aureliolo May 3, 2026
2ceba78
fix: babysit round 5, address CodeRabbit review on the round-4 head
Aureliolo May 3, 2026
4f8a47e
fix: address round-6 CodeRabbit findings on PR #1744
Aureliolo May 3, 2026
c56b1d9
fix: address round-7 CodeRabbit findings on PR #1744
Aureliolo May 4, 2026
e155e08
fix: address round-8 CodeRabbit findings on PR #1744
Aureliolo May 4, 2026
9ed706f
fix: address round-9 CodeRabbit findings on PR #1744
Aureliolo May 4, 2026
0cfa0df
fix: address round-10 CodeRabbit findings on PR #1744
Aureliolo May 4, 2026
cc7b04d
fix: align McpInstallationRepository CRUD vocab to list_items
Aureliolo May 4, 2026
1c4bb1f
fix: align in_memory MCP installations validation + add conformance gate
Aureliolo May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ See [docs/reference/configuration-precedence.md](docs/reference/configuration-pr
- **Docstrings**: Google style, required on public classes / functions (ruff D rules).
- **Immutability**: create new objects, never mutate existing ones. Frozen Pydantic models for config/identity; for non-Pydantic registries use `copy.deepcopy()` at construction + `MappingProxyType` wrapping; deepcopy at system boundaries (tool execution, provider serialization, persistence).
- **Config vs runtime state**: frozen models for config/identity; separate mutable-via-copy models (`model_copy(update=...)`) for runtime state that evolves. Never mix static config and mutable runtime fields in one model.
- **Pydantic v2 conventions**: `ConfigDict(frozen=True, allow_inf_nan=False)` everywhere; `extra="forbid"` on request DTOs; `@computed_field` for derived values; `NotBlankStr` from `core.types` for identifier / name fields. See [docs/reference/conventions.md](docs/reference/conventions.md) §10.
- **Pydantic v2 conventions**: `ConfigDict(frozen=True, allow_inf_nan=False)` everywhere; `extra="forbid"` on every model that does not need to round-trip through `model_dump()` (request DTOs always; ~489 ConfigDicts total today; carve-out is the ~46 classes carrying a `@computed_field`); `@computed_field` for derived values; `NotBlankStr` from `core.types` for identifier / name fields. See [docs/reference/conventions.md](docs/reference/conventions.md) §10.
- **Args models at every system boundary (#1611)**: every `BaseTool` subclass, MCP tool registration, A2A RPC method, and WebSocket event declares a typed Pydantic args model and is validated before dispatch. See [docs/reference/conventions.md](docs/reference/conventions.md) §9 for the inventory and [docs/reference/mcp-handler-contract.md](docs/reference/mcp-handler-contract.md) for the MCP-specific contract.
- **Typed-boundary helper**: every entry-point that ingests a dict payload from an external source (MCP handler args, JWT decode, WebSocket control message, audit-chain payload, A2A JSON-RPC params, settings security import) calls `parse_typed()` from `synthorg.api.boundary`. The helper accepts either a Pydantic model class or a `TypeAdapter` (for discriminated unions); it validates, emits `API_BOUNDARY_VALIDATION_FAILED` on failure with the boundary name + redacted error description + first 5 field locations + truncated flag, and re-raises `ValidationError` for the caller to translate into the appropriate HTTP / RPC / envelope response. The `boundary` label MUST be a hardcoded `LiteralString` -- never user-controlled. Phase 3 lint guard `scripts/check_boundary_typed.py` enforces the contract: a regression at any of the six registered (file, function) pairs fails pre-push and CI. See [docs/reference/typed-boundaries.md](docs/reference/typed-boundaries.md) for the full per-boundary inventory and the "Adding a new boundary" recipe.
- **Async concurrency**: prefer `asyncio.TaskGroup` for fan-out / fan-in. Wrap independent task bodies in `async def` helpers that catch `Exception` (re-raise only `MemoryError` / `RecursionError`) so one failure doesn't unwind the group. See [docs/reference/conventions.md](docs/reference/conventions.md) §11.
Expand Down
67 changes: 57 additions & 10 deletions cli/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,17 @@ Running 'synthorg config' without a subcommand shows the current configuration
var configShowCmd = &cobra.Command{
Use: "show",
Short: "Display current configuration",
Args: cobra.NoArgs,
RunE: runConfigShow,
Long: `Display the resolved configuration as a single block.

Renders every key from the config file alongside its current
value. If the config file is missing the command reports
"Not initialized" rather than rendering built-in defaults; use
'synthorg config list' for per-key resolution and source
attribution that still surfaces the default-value column.`,
Example: ` synthorg config show # human-readable summary
synthorg --json config show # JSON for scripts`,
Args: cobra.NoArgs,
RunE: runConfigShow,
}

var configGetCmd = &cobra.Command{
Expand Down Expand Up @@ -94,6 +103,9 @@ Supported keys:

Plus 17 runtime tunables (registry host, image tags, timeouts, size
limits, NATS defaults). See cli/CLAUDE.md for the full list.`,
Example: ` synthorg config get backend_port
synthorg config get channel
synthorg config get image_tag`,
Args: cobra.ExactArgs(1),
RunE: runConfigGet,
ValidArgsFunction: completeConfigGetKeys,
Expand Down Expand Up @@ -137,14 +149,26 @@ max_binary_bytes, max_archive_entry_bytes). See cli/CLAUDE.md for formats.
Keys that affect Docker compose (backend_port, web_port, sandbox, docker_sock,
image_tag, log_level, telemetry_opt_in, fine_tuning, fine_tuning_variant, and
the registry/NATS tunables) trigger automatic compose.yml regeneration.`,
Example: ` synthorg config set backend_port 3001
synthorg config set channel dev
synthorg config set hints always
synthorg config set telemetry_opt_in true`,
Args: cobra.ExactArgs(2),
RunE: runConfigSet,
ValidArgsFunction: completeConfigSetKeys,
}

var configUnsetCmd = &cobra.Command{
Use: "unset <key>",
Short: "Reset a configuration key to its default value",
Use: "unset <key>",
Short: "Reset a configuration key to its default value",
Long: `Remove a config-file override so the key falls back to its default.

Use this rather than 'config set <key> <default>' when you want
the key to follow future default changes (defaults can move
between releases). Compose-affecting keys trigger compose.yml
regeneration after the unset lands.`,
Example: ` synthorg config unset backend_port # reset to platform default
synthorg config unset channel # follow default channel`,
Args: cobra.ExactArgs(1),
RunE: runConfigUnset,
ValidArgsFunction: completeConfigUnsetKeys,
Expand All @@ -153,22 +177,45 @@ var configUnsetCmd = &cobra.Command{
var configListCmd = &cobra.Command{
Use: "list",
Short: "Show all config keys with resolved value and source",
Args: cobra.NoArgs,
RunE: runConfigList,
Long: `List every settable config key with its resolved value and source.

Source is one of "default", "config", or "env" (env vars
override the config file but cannot be set via 'config set').
Useful for debugging precedence when a value disagrees with what
'config show' implies.`,
Example: ` synthorg config list # full table
synthorg --json config list # JSON, one row per key`,
Args: cobra.NoArgs,
RunE: runConfigList,
}

var configPathCmd = &cobra.Command{
Use: "path",
Short: "Print the config file path",
Args: cobra.NoArgs,
RunE: runConfigPath,
Long: `Print the absolute path to the config file the CLI uses.

The path is platform-appropriate (XDG-compatible on Linux, the
native config dir on macOS / Windows) and reflects --data-dir or
SYNTHORG_DATA_DIR overrides if set.`,
Example: ` synthorg config path # print path
cat "$(synthorg config path)" # inspect raw file
synthorg config path --data-dir=/tmp/x`,
Args: cobra.NoArgs,
RunE: runConfigPath,
}

var configEditCmd = &cobra.Command{
Use: "edit",
Short: "Open config file in your editor",
Args: cobra.NoArgs,
RunE: runConfigEdit,
Long: `Open the config file in $EDITOR (or VISUAL) for direct edits.

Falls back to a platform-appropriate editor when neither env var
is set (vim on POSIX, notepad on Windows). The CLI re-reads the
file on the next invocation; no daemon to restart.`,
Example: ` synthorg config edit # use $EDITOR
EDITOR=nano synthorg config edit # one-shot override`,
Args: cobra.NoArgs,
RunE: runConfigEdit,
}

func init() {
Expand Down
4 changes: 3 additions & 1 deletion cli/cmd/doctor_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ var doctorReportCmd = &cobra.Command{
Use: "report",
Short: "Generate a diagnostic archive and bug report URL",
Long: "Collects diagnostics, saves a report file, and prints a pre-filled GitHub issue URL.",
RunE: runDoctorReport,
Example: ` synthorg doctor report # write archive + print issue URL
synthorg doctor report --json # machine-readable summary`,
RunE: runDoctorReport,
}

func init() {
Expand Down
8 changes: 8 additions & 0 deletions cli/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ var (
var startCmd = &cobra.Command{
Use: "start",
Short: "Pull images and start the SynthOrg stack",
Long: `Start every container in the SynthOrg compose stack.

By default this pulls each image (verifying signatures and SLSA
attestations against the pinned digests) before bringing the stack
up detached, then waits for the backend's /api/v1/readyz to return
healthy. Pass --no-pull to skip the pull when iterating locally,
--no-detach to stream logs in the foreground, or --dry-run to print
the docker commands the run would issue without executing them.`,
Example: ` synthorg start # pull, verify, and start
synthorg start --no-pull # start without pulling images
synthorg start --dry-run # preview what would happen
Expand Down
8 changes: 8 additions & 0 deletions cli/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ var (
var statusCmd = &cobra.Command{
Use: "status",
Short: "Show container states, health, and versions",
Long: `Render a one-shot snapshot of the running SynthOrg stack.

Combines a verdict banner (OK / DEGRADED / CRITICAL), the backend
/api/v1/readyz response, the per-container table from
docker compose ps, and live resource usage. Use --watch to refresh
on an interval, --wide for port columns, --services to filter by
name, or --check for a silent exit-code-only run intended for
scripts (0 healthy, 3 unhealthy, 4 unreachable).`,
Example: ` synthorg status # show current status
synthorg status --watch # continuously poll
synthorg status --wide # show extra columns
Expand Down
7 changes: 7 additions & 0 deletions cli/cmd/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ var (
var stopCmd = &cobra.Command{
Use: "stop",
Short: "Stop the SynthOrg stack",
Long: `Stop every container in the SynthOrg compose stack.

Sends SIGTERM and waits for the configured graceful shutdown
window before falling back to SIGKILL. Pass --timeout to override
the wait, or --volumes to also remove named volumes once the stack
is down (destroys persisted data; pair with 'synthorg backup
create' first).`,
Example: ` synthorg stop # graceful shutdown
synthorg stop --timeout 60s # custom shutdown timeout
synthorg stop --volumes # stop and remove volumes`,
Expand Down
8 changes: 8 additions & 0 deletions cli/cmd/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ var (
var uninstallCmd = &cobra.Command{
Use: "uninstall",
Short: "Stop containers, remove data, and uninstall SynthOrg",
Long: `Tear down the SynthOrg installation.

Stops every container, removes named volumes, deletes the data
directory, and removes pulled images. Each destructive step is
confirmed interactively unless --yes is set. Pass --keep-data to
preserve the data directory and config (useful before a clean
re-install) or --keep-images to leave pulled images on disk for
faster re-init later.`,
Example: ` synthorg uninstall # interactive uninstall (prompts for each step)
synthorg uninstall --yes # non-interactive full uninstall
synthorg uninstall --keep-data # uninstall but preserve config and data
Expand Down
10 changes: 10 additions & 0 deletions cli/cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ var (
var updateCmd = &cobra.Command{
Use: "update",
Short: "Update CLI, refresh compose template, and pull new container images",
Long: `Bring the local installation up to the channel's latest version.

Self-updates the CLI binary, regenerates compose.yml from the
embedded template, then pulls the matching container images
(verifying signatures and SLSA attestations) and restarts the
running stack. Pass --cli-only or --images-only to scope the
update, --check to exit 10 if an update is available without
applying it, --dry-run to preview the planned changes, or
--no-restart to pull images but leave the running containers
untouched.`,
Example: ` synthorg update # update CLI + images
synthorg update --cli-only # update CLI binary only
synthorg update --images-only # update container images only
Expand Down
5 changes: 5 additions & 0 deletions cli/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ var versionShort bool
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print CLI version and build info",
Long: `Print the CLI version, commit hash, and build date.

The default form renders a logo banner plus build metadata
suitable for issue reports. Pass --short for a single-line
semantic version string, useful in shell pipelines.`,
Example: ` synthorg version # full version info with logo
synthorg version --short # version number only`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
25 changes: 17 additions & 8 deletions docs/reference/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ApprovalRepository(Protocol):
self,
*,
status: ApprovalStatus | None = None,
limit: int | None = None,
limit: int = 100,
offset: int = 0,
) -> tuple[ApprovalItem, ...]: ...
async def delete(self, approval_id: NotBlankStr) -> bool: ...
Expand Down Expand Up @@ -145,13 +145,22 @@ inline with the consumer. Examples:
## 8. Frozen `ConfigDict` pattern

Every Pydantic model declares
`model_config = ConfigDict(frozen=True, allow_inf_nan=False)`. Request
DTOs additionally set `extra="forbid"` so unknown keys are rejected
instead of silently ignored. Combined with the framework's `frozen`
guarantee this gives us the "create new objects, never mutate
existing ones" property the immutability covenant relies on.

References: 30+ occurrences across `src/synthorg/`. Canonical example:
`model_config = ConfigDict(frozen=True, allow_inf_nan=False)`. The
project standard is to add `extra="forbid"` on every model that does
not need to round-trip through `model_dump()` -- which is most of
them. Around 489 ConfigDicts across `src/synthorg/` carry the strict
form today; the carve-out is the ~46 classes that declare a
`@computed_field`, where Pydantic v2 includes the computed value in
`model_dump()` output and a strict-extra reconstruction would reject
that key on the round trip. Request DTOs are always strict because
the caller-side reject-unknown-keys property is what `extra="forbid"`
exists for.

Combined with the framework's `frozen` guarantee this gives us the
"create new objects, never mutate existing ones" property the
immutability covenant relies on.

References: 489+ occurrences across `src/synthorg/`. Canonical example:
`src/synthorg/approval/models.py:28`.

## 9. Typed args models at system boundaries (#1611)
Expand Down
53 changes: 23 additions & 30 deletions scripts/mock_spec_baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ tests/integration/integrations/test_controllers.py:877:30
tests/integration/integrations/test_controllers.py:885:17
tests/integration/integrations/test_controllers.py:886:25
tests/integration/integrations/test_controllers.py:887:30
tests/integration/integrations/test_controllers.py:969:18
tests/integration/integrations/test_controllers.py:970:31
tests/integration/integrations/test_controllers.py:971:34
tests/integration/integrations/test_controllers.py:972:30
tests/integration/integrations/test_controllers.py:1009:25
tests/integration/integrations/test_controllers.py:1060:18
tests/integration/integrations/test_controllers.py:1061:27
tests/integration/integrations/test_controllers.py:1078:18
tests/integration/integrations/test_controllers.py:1079:31
tests/integration/integrations/test_controllers.py:996:18
tests/integration/integrations/test_controllers.py:997:31
tests/integration/integrations/test_controllers.py:998:34
tests/integration/integrations/test_controllers.py:999:30
tests/integration/integrations/test_controllers.py:1036:25
tests/integration/integrations/test_controllers.py:1087:18
tests/integration/integrations/test_controllers.py:1088:27
tests/integration/integrations/test_controllers.py:1105:18
tests/integration/integrations/test_controllers.py:1106:31
tests/integration/integrations/test_oauth_flows.py:51:11
tests/integration/integrations/test_oauth_flows.py:54:28
tests/integration/integrations/test_oauth_flows.py:84:22
Expand Down Expand Up @@ -427,21 +427,15 @@ tests/unit/api/test_app.py:420:32
tests/unit/api/test_app.py:423:31
tests/unit/api/test_app.py:454:18
tests/unit/api/test_app.py:455:23
tests/unit/api/test_app.py:477:21
tests/unit/api/test_app.py:478:27
tests/unit/api/test_app.py:479:26
tests/unit/api/test_app.py:520:21
tests/unit/api/test_app.py:521:27
tests/unit/api/test_app.py:522:26
tests/unit/api/test_app.py:943:24
tests/unit/api/test_app.py:1004:28
tests/unit/api/test_app.py:1062:30
tests/unit/api/test_app.py:1063:36
tests/unit/api/test_app.py:1066:35
tests/unit/api/test_app.py:1447:18
tests/unit/api/test_app.py:1448:15
tests/unit/api/test_app.py:1458:18
tests/unit/api/test_app.py:1471:18
tests/unit/api/test_app.py:949:24
tests/unit/api/test_app.py:1010:28
tests/unit/api/test_app.py:1068:30
tests/unit/api/test_app.py:1069:36
tests/unit/api/test_app.py:1072:35
tests/unit/api/test_app.py:1453:18
tests/unit/api/test_app.py:1454:15
tests/unit/api/test_app.py:1464:18
tests/unit/api/test_app.py:1477:18
tests/unit/api/test_approval_store.py:309:19
tests/unit/api/test_approval_store.py:329:19
tests/unit/api/test_auto_wire_meetings.py:24:11
Expand Down Expand Up @@ -624,10 +618,9 @@ tests/unit/communication/bus/test_nats_consumer_config.py:31:9
tests/unit/communication/bus/test_nats_consumer_config.py:33:24
tests/unit/communication/bus/test_nats_consumer_config.py:33:47
tests/unit/communication/bus/test_nats_consumer_config.py:44:12
tests/unit/communication/loop_prevention/test_circuit_breaker.py:293:15
tests/unit/communication/loop_prevention/test_circuit_breaker.py:294:20
tests/unit/communication/loop_prevention/test_circuit_breaker.py:316:15
tests/unit/communication/loop_prevention/test_circuit_breaker.py:317:24
tests/unit/communication/loop_prevention/test_circuit_breaker.py:300:20
tests/unit/communication/loop_prevention/test_circuit_breaker.py:324:24
tests/unit/communication/loop_prevention/test_circuit_breaker.py:362:24
tests/unit/communication/meeting/test_agent_caller.py:102:25
tests/unit/communication/meeting/test_agent_caller.py:104:15
tests/unit/communication/meeting/test_agent_caller.py:106:28
Expand Down Expand Up @@ -1372,8 +1365,8 @@ tests/unit/hr/performance/test_tracker_enhancements.py:232:24
tests/unit/hr/performance/test_tracker_enhancements.py:233:30
tests/unit/hr/performance/test_tracker_enhancements.py:237:23
tests/unit/hr/performance/test_tracker_enhancements.py:239:30
tests/unit/hr/pruning/test_service.py:566:19
tests/unit/hr/pruning/test_service.py:600:19
tests/unit/hr/pruning/test_service.py:568:19
tests/unit/hr/pruning/test_service.py:602:19
tests/unit/hr/test_activity_list_recent.py:50:11
tests/unit/hr/test_activity_list_recent.py:57:14
tests/unit/hr/test_activity_list_recent.py:58:31
Expand Down
8 changes: 4 additions & 4 deletions src/synthorg/a2a/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class A2AAuthConfig(BaseModel):
outbound_scheme: Default auth scheme for outbound requests.
"""

model_config = ConfigDict(frozen=True, allow_inf_nan=False)
model_config = ConfigDict(frozen=True, allow_inf_nan=False, extra="forbid")

inbound_scheme: A2AAuthScheme = Field(
default="api_key",
Expand Down Expand Up @@ -67,7 +67,7 @@ class A2APushConfig(BaseModel):
protection.
"""

model_config = ConfigDict(frozen=True, allow_inf_nan=False)
model_config = ConfigDict(frozen=True, allow_inf_nan=False, extra="forbid")

enabled: bool = False
signature_algorithm: A2ASignatureAlgorithm = Field(
Expand Down Expand Up @@ -98,7 +98,7 @@ class A2AAgentCardVerificationConfig(BaseModel):
verification.
"""

model_config = ConfigDict(frozen=True, allow_inf_nan=False)
model_config = ConfigDict(frozen=True, allow_inf_nan=False, extra="forbid")

enabled: bool = False
require_signatures: bool = False
Expand Down Expand Up @@ -152,7 +152,7 @@ class A2AConfig(BaseModel):
agent_card_verification: Agent Card signature verification.
"""

model_config = ConfigDict(frozen=True, allow_inf_nan=False)
model_config = ConfigDict(frozen=True, allow_inf_nan=False, extra="forbid")

enabled: bool = False
allowed_peers: tuple[NotBlankStr, ...] = ()
Expand Down
Loading
Loading