Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ behavior separately; do not reproduce observed upstream server failures as
compatibility behavior. See `contracts/agents-api/list-query-semantics.md` for the
bounded evidence.

Report validation failures with official evidence through the typed field error,
which emits `invalid_request_error` with the observed param and message; keep
other local codes until their official fields are sampled. A malformed path
identifier must produce exactly the response of a well-formed missing one on that
route, including invalid bodies, queries and storage availability: resolve it to
the never-assigned maximum UUID and let the missing path run, or reject it
directly only where the lookup is the next check. Malformed list cursors and
request-body references keep their own errors. Reject U+0000 in metadata
explicitly with its `metadata.<key>` param; other stored strings rely on the
PostgreSQL error mapping, so keep each request's writes in one transaction. See
`contracts/agents-api/official-semantics-alignment.md`.

Keep runtime state, test artifacts and build output under `~/.parsar/`. Require
absolute user-supplied working directories. Keep credentials out of source and
logs. Update this guide when architecture, ownership or generated contracts change.
Expand Down
8 changes: 6 additions & 2 deletions contracts/agents-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ upgrade the protocol.
authentication and Beta header as other resources. The response contains only
`id`, `object: vault`, `created_at`, `name` and `metadata`. Omitted name stays null;
explicit null is rejected. Supplied strings are trimmed and must contain 1–256
UTF-8 bytes. Omitted/null metadata becomes `{}` and values must be strings.
UTF-8 bytes. Omitted/null metadata becomes `{}`; a non-string value returns
`invalid_request_error` with param `metadata.<key>`.
Session-specific metadata pair/character limits do not apply. The existing
64 KiB encoded metadata and 1 MiB HTTP body bounds are local implementation
limits. Creation does not start execution. Retrieval maps missing, malformed and
Expand Down Expand Up @@ -479,7 +480,10 @@ The Store's internal DTO is not the upstream response model. The API layer must
validate and resolve the upstream schema before persistence, and report only
supported options. For example, upstream metadata is limited to 16 pairs with
64-character keys and 512-character values; a storage byte limit is not a
replacement for that public validation.
replacement for that public validation. Violations return `invalid_request_error`
with the official `metadata` or `metadata.<key>` param. U+0000 in stored strings
is a local PostgreSQL limit and returns 400 without writing; see the
[validation error batch](official-semantics-alignment.md#validation-error-fields--september-23).

Use the pinned official Python client against the actual service, with response
validation enabled, for supported Session/Turn/Items operations, pagination, streaming,
Expand Down
11 changes: 6 additions & 5 deletions contracts/agents-api/list-query-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ unsampled inputs:
These remain registered differences and are not changed here: repeated Files
`purpose` values (SFT-18); unsampled overflowing limits; unknown and repeated keys
on the Environment Files list, which keeps its own strict parser (its limit range
errors now use the Beta code through the shared limit reader); malformed non-UUID
path IDs (SES-28); metadata and name error envelopes (VA-07/08/09) and U+0000
(VA-10); Skill sole-version deletion and number reuse (SFT-01/02); Session deletion
lifecycle (SES-29/30); whitespace input (SES-01..04); Template network forms and
codes (SFT-20/21/22); and response defaults (VA-11, SES-23/25).
errors now use the Beta code through the shared limit reader); Skill sole-version
deletion and number reuse (SFT-01/02); Session deletion lifecycle (SES-29/30);
whitespace input (SES-01..04); Template network forms (SFT-21/22); and response
defaults (VA-11, SES-23/25). Malformed path IDs (SES-28), metadata and name error
fields (VA-07/08/09), U+0000 (VA-10) and Template network codes (SFT-20) are
addressed by the [validation error batch](official-semantics-alignment.md#validation-error-fields--september-23).

### Acceptance boundary

Expand Down
62 changes: 62 additions & 0 deletions contracts/agents-api/official-semantics-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,65 @@ checks passed independently. Rebase onto main `6a3131e` preserved every batch pa
The combined tree at `4981580` passed API, execution, contract and dedicated-PostgreSQL
Environment scheduling/initial-input/creation-stream regressions. No new E2B,
OAuth provider or native capability combination was qualified.

## Validation error fields — September 23

This batch aligns validation failures that Core already rejected with the
official `code` and `param` fields. It does not change any limit. Evidence comes
from the campaign scan at main `284cbcf`, recorded privately in
`~/.parsar/remediation/20260923/campaign-scan-1/{vaults-agents,sessions,skills-files-templates}/findings.json`
(VA-07, VA-08, VA-09, VA-10, SES-28 and SFT-20), plus the September 22 Session
observation that `{"metadata":{"a":null}}` returns param `metadata.a`.

| Row | Case | Core behavior |
| --- | --- | --- |
| M1–M3 | More than 16 metadata pairs, a key over 64 characters, a value over 512 characters (Agent create/update, Session create/update) | 400 with type and code `invalid_request_error`, param `metadata` or `metadata.<key>`, and the observed official message with the actual count or length. Pairs are checked before keys and values, and keys in sorted order. |
| M4 | A non-string metadata value: integer, number, boolean, object, array or null (Agent create/update, Session create/update and Vault create; neither Core nor the pinned SDK has a Vault update) | 400 `invalid_request_error`, param `metadata.<key>`, message `Invalid type for 'metadata.<key>': expected a string, but got <kind> instead.` The first such value in document order is reported before the generic whole-body error. Templates accept no metadata. |
| M5 | Vault metadata size | Unchanged: no pair or length limits, only the local 64 KiB storage bound. |
| N1 | Agent `name` over 128 characters | 400 `invalid_request_error`, param `name`, observed message. Empty and untrimmed names stay accepted. |
| U1 | U+0000 in a stored string | Never 500 and nothing is written. Metadata keys and values report `metadata.<key>`; other strings return 400 `invalid_request_error` with a null param. This is a local limit: PostgreSQL text and jsonb cannot store U+0000, while the official service accepts and echoes it. |
| I1/I2 | A malformed path identifier on any Beta resource route, and on Files, Skills and Skill versions | Byte-for-byte the response of a well-formed missing identifier on that route, including invalid bodies and queries, and a deployment without credential encryption. Foreign, missing and malformed identifiers stay indistinguishable. |
| T1 | Template network rejections (wildcard, port, scheme, IPv6, empty host, empty/null/omitted list with `restricted`, more than 100 domains, domains with another access) and the shared inline Session network | 400 `invalid_request_error` with a null param. Accepted hostname forms are unchanged; other unsupported installation fields keep `unsupported_or_invalid_configuration`. |

Decisions:

- A typed field error carries the param and message through the existing error
writer. Metadata type errors are found by reading the metadata object in
document order before generic decoding; limit checks keep their previous
position, so validation order relative to lookups (SES-33) is unchanged.
- U+0000 is checked explicitly in metadata, so the param is exact. All other
stored strings rely on mapping PostgreSQL `22021` (U+0000 or invalid UTF-8 in
a text parameter) and `22P05` (`\u0000` in jsonb) to 400 with the generic
message "Request text contains characters this service cannot store or compare,
such as U+0000 or invalid UTF-8." The same mapping covers query filters, for
example `agent_id=%ff` on the Session list. The persisted string fields are too
many to check one by one, and the database is the single place that knows which
strings are stored. The failing statement aborts its transaction; real-PostgreSQL
tests compare every public table before and after the rejected requests.
- A malformed path identifier resolves to the maximum UUID, which Core never
assigns because it only generates version 4 and 5 UUIDs. The request then
follows exactly the missing-identifier path, including body, query and storage
checks. Routes whose lookup is the next check keep their direct not-found
response. Malformed list cursors and request-body references are unchanged:
Session, Turn, Item, Subagent, Artifact, Agent, Vault and Credential cursors
still return 400 `invalid_request`, and Template cursors keep their existing
not-found response.
- Network messages are Core wording; the official prose is not copied.
- Documented message difference for M2: the official message abbreviated a
65-character key as `'KKK...KKK'`. That single sample of identical characters
cannot reveal the abbreviation rule, so Core quotes the full key. Status, type,
code and param match.

Deferred and unchanged: accepting and storing U+0000; hostname forms accepted
officially (SFT-21) and `disabled` with domains, which the official service
accepts (SFT-22); non-canonical UUID spellings such as uppercase, braces or
`urn:uuid:` still resolve to the same resource; Skill sole-version deletion and
number reuse; Session deletion lifecycle; whitespace input; response defaults;
the Environment Files list query parser; and the Files `limit=abc` code.

Go handler tests cover every row. Real-PostgreSQL tests replay every path-ID
route for malformed, missing and foreign identifiers (tenant B), with valid and
invalid bodies and queries, and replay U+0000 on every create/update family with
a database digest proving no writes. The pinned-SDK acceptance scripts assert the
new codes, params and messages. Independent real-Core acceptance is recorded
separately by the coordinator.
35 changes: 24 additions & 11 deletions contracts/agents-api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2196,7 +2196,10 @@ paths:
post:
consumes:
- application/json
description: 'Persists configuration independently of execution. Supports model/name/instructions/metadata,
description: 'Persists configuration independently of execution. Names over
128 characters and metadata outside 16 string pairs with 64-character keys
and 512-character values return invalid_request_error with the official param;
U+0000 in stored strings is rejected as a local storage limit. Supports model/name/instructions/metadata,
explicit reasoning and service tiers, multi_agent, text/json_schema, function/tool_search/programmatic_tool_calling
and HTTP MCP with nullable credential_id and explicit service origin and boolean
required defaulting to false. Saving credential_id grants no access: Session
Expand Down Expand Up @@ -2343,8 +2346,9 @@ paths:
- application/json
description: Preserves omitted fields and replaces supplied fields using shared
saved-configuration validation. Null name/instructions clear; null or empty
metadata clears all pairs. Existing Session snapshots are unchanged. Empty
updates advance updated_at without changing saved fields. Nested replacement/null
metadata clears all pairs. Name and metadata validation errors return invalid_request_error
with the official param. Existing Session snapshots are unchanged. Empty updates
advance updated_at without changing saved fields. Nested replacement/null
defaults, model-derived reasoning and exact hosted error behavior remain incompletely
verified.
parameters:
Expand Down Expand Up @@ -2699,7 +2703,8 @@ paths:
inline/referenced Skill ZIPs, Plugin ZIPs and workspace-contained capability
directories. Omitted/null network defaults to enabled. Restricted network
requires 1–100 exact ASCII hostnames; other host forms and populated unsupported
installations are rejected before persistence without echoing input. No compute
installations are rejected before persistence without echoing input. Network
policy rejections return invalid_request_error with a null param. No compute
is allocated. Exact hosted error/retry semantics remain unverified.
parameters:
- description: agents=v1
Expand Down Expand Up @@ -2839,7 +2844,8 @@ paths:
are encrypted and omitted from responses. Capability directories are snapshotted
after setup. Environment MCP execution requires a qualified native transport
and runtime network policy. Empty updates advance updated_at without changing
saved fields or confidential contents.
saved fields or confidential contents. Network policy rejections return invalid_request_error
with a null param.
parameters:
- description: agents=v1
in: header
Expand Down Expand Up @@ -2986,7 +2992,9 @@ paths:
An attached Vault with no matching credential may remain anonymous; missing
keys or failed credential lookup/decryption never fall back to anonymous execution.
Omitted stream defaults to false; stream and agent_id cannot be null. Metadata
may be null, but its values must be strings. Initial input accepts a string
may be null; non-string values and limit violations return invalid_request_error
with a metadata or metadata.<key> param. Hosted network policy rejections
return invalid_request_error with a null param. Initial input accepts a string
or ordered user-message array. Codex and Claude SDK on none and qualified
openai_hosted also accept inline PNG/JPEG image content; other image combinations
and remote URLs are unsupported. None initial input atomically starts a Turn;
Expand Down Expand Up @@ -3199,9 +3207,12 @@ paths:
- application/json
description: The metadata field is required in an update body. Send null or
{} to clear it, or supply an object to replace all pairs. Up to 16 string
pairs, with keys at most 64 characters and values at most 512 characters.
Execution configuration and activity are unchanged. Returns the same safe
Environment and pending-input activity projection as Session retrieval.
pairs, with keys at most 64 characters and values at most 512 characters;
violations and non-string values return invalid_request_error with a metadata
or metadata.<key> param. U+0000 is rejected as a local storage limit. Malformed,
missing and foreign Session IDs share the not-found response. Execution configuration
and activity are unchanged. Returns the same safe Environment and pending-input
activity projection as Session retrieval.
parameters:
- description: agents=v1
in: header
Expand Down Expand Up @@ -4824,8 +4835,10 @@ paths:
description: Creates a project-owned Vault independently of execution. Omitted
name stays null; a supplied string is trimmed and must contain 1–256 UTF-8
bytes. Explicit null name is invalid. Omitted/null metadata becomes an empty
object; values must be strings. Metadata has a local 64 KiB encoded storage
bound. Credentials, Session binding and hosted error/retry parity remain incomplete.
object; non-string values return invalid_request_error with a metadata.<key>
param. Metadata has a local 64 KiB encoded storage bound. U+0000 in stored
strings is rejected as a local storage limit. Credentials, Session binding
and hosted error/retry parity remain incomplete.
parameters:
- description: agents=v1
in: header
Expand Down
Loading
Loading