Repository navigation
feat(website)!: WebsiteEnvironment serves a site on the website/v1 contract, and a conformance check tests any server (FD-4683) - #428
Conversation
Brings in #428's review fixes beside this branch's: the clock's sync reads under the refresh lock and keeps the answered rate; conformance checks X-Universe-Now last, on every answer, at the recommended level, with the reserved-path probes counted; the handwritten test server stamps its 404s and 405s like every other answer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
f5523cf to
7fc14cd
Compare
…tract, and a conformance check tests any server (FD-4683) This PR's change, rebased as one commit onto #427. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7fc14cd to
b18049d
Compare
3579c2a to
76145d2
Compare
… keep older sites and shared names working (FD-4683) - The gateway answers 503 on a site's route while its card can't be read, rather than forwarding with nothing reserved. - A plugin's MultiEnv record looks each child up by its kind, so a website and an MCP server may share a name. - The state verifier reads a site its definition names but its card doesn't list at /svc/<name>/go, deprecated. - The clock script is put in streamed HTML holding only what comes before <head>. - Conformance fails a website/v1 ready that answers an HTTP error, whatever its body says. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ver HTTP client the test client uses (FD-4683) After a sign-out the client's jar keeps the site's signed-out cookie under testserver.local, and setting the forged one adds a second cookie of the same name. httpx2, which Starlette's TestClient uses when it is installed (as on CodeBuild), sends the forged one first, so the site reads the valid signed-out cookie and the test fails; httpx sends them the other way round. Clearing the jar first sends the forged cookie alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This is a huge red flag: it leaks CUA-Gym into agentenv-framework.
|
|
Following up on my
Concretely:
|
|
Agreed, and done in this stack rather than deferred.
Each PR removes what it had added, so the stack never introduces any of this. Grading a website now belongs outside the framework: |
1ffec4c to
05da818
Compare
…op /go, the state op and website_state_verifier (FD-4683) - The SDK serves no `/go`, no `params.state.path` and no `website/v1` `state` op; `STATE_PATH`, `WebsiteState`, `website_state`, `website_sites` and `website/state.py` are gone, and the default store keeps no initial state or diff. - Conformance runs the data plane's round trip on every server, with no skip for a state that shows changes. - The gateway reserves the card's `url` and every advertised endpoint, no longer a state path. - `website_state_verifier` is removed from the framework; grading against a site's state belongs outside it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
05da818 to
33ad3b8
Compare
… items in OpenAPI, and find the clock's sync_time by name (FD-4683) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@greptile-apps all three remaining review comments are fixed in d4efdbb, each with a test that fails without its fix:
The full unit suite passes on this commit (12,908) and lint is clean. All three threads are replied to and resolved. |
|
Confirmed: all three findings are fixed in
These comments can remain resolved. I verified the code and tests in the commit; I did not independently run the suite. |
…ses the conformance check (FD-4683) This PR's change, rebased as one commit onto #428. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… per request, and conformance stays on the server it checks (FD-4683) - The SDK no longer keeps a store: `self.store`, `Collections`, `@website(data=...)` and the default data hooks are gone. A site serves its data through `@reset_data`, `@add_data` and `@get_data` as any AgentEnvEnvironment does, usually over its `database_url`; `acting_user()` is nobody unless a site says else. - The clock is read once per request, as MCP servers read it: no extrapolation, rate inference or refresh lock, so a frozen gateway clock is exactly frozen and a slow one holds up only its own request. An unarmed clock (404) is wall time; an unreachable one answers 503, but the data plane and the card answer without the header. `sync_time` with an empty URL goes back to wall time. - Conformance sends nothing off the server under check: a card's path or URL naming another scheme, host or port fails that check. The clock check ends by syncing the server back to wall time. - Plain `def` `@api` handlers run in a worker thread; a route whose type hints can't be resolved fails the app's build; session cookies go on successful answers only. - The static fallback serves no dot-named path and treats a path that can't be a file's as not found; a time parses with any fraction of a second on Python 3.10; the clock script goes after `<head>` (not `<header>`), else after the doctype and `<html>` tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…session answers a storage state (FD-4932)
- The session cookie carries Max-Age, never an absolute Expires, so a browser on another clock judges it the same.
- website/v1's session answers {cookies, local_storage}, each cookie with max_age, in browser/v1's shape.
- The gateway provider gives each site its own WEBSITE_SESSION_SECRET at deploy, so its sessions outlive a restart
or a snapshot reload, which keeps the compose file it recreates from.
- Conformance warns, without failing the run, on a cookie with an absolute Expires and no Max-Age.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| return {"name": SESSION_COOKIE, "value": self.encode(user, ttl_s), "path": "/", "max_age": ttl_s, | ||
| "httpOnly": True, "sameSite": "Lax"} |
There was a problem hiding this comment.
Browser rejects session cookies
SessionSigner.cookie() now returns max_age, but the browser reader in PR #447 uses Cookie with extra="forbid" and accepts only expires. Sending a cookie minted by website/v1 to that PR's cookies/set fails with invalid_params instead of signing the browser in. The same reader remains unchanged through PR #469.
Update the browser cookie model and its Playwright/DevTools conversions alongside this change so they accept and apply the relative lifetime.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agentenv-protocol/src/agentenv_protocol/website/session.py
Line: 51-52
Comment:
**Browser rejects session cookies**
`SessionSigner.cookie()` now returns `max_age`, but the browser reader in PR #447 uses `Cookie` with `extra="forbid"` and accepts only `expires`. Sending a cookie minted by `website/v1` to that PR's `cookies/set` fails with `invalid_params` instead of signing the browser in. The same reader remains unchanged through PR #469.
Update the browser cookie model and its Playwright/DevTools conversions alongside this change so they accept and apply the relative lifetime.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Stacked on #427.
agentenv_protocol.website: the easy path to a synthetic website that follows website/v1 (urn:agentenv:website/v1).@api.get/post/put/patch/delete(path, name=, roles=, status=): path, query and body are bound from type hints, errors are exceptions mapped to statuses, and an OpenAPI 3.1 document is generated.ApiContext: the signed-in user (else the acting user, so the agent starts signed in), the caller's role, and the public URL fromX-Forwarded-Host/-Proto.AgentEnv-Rolea browser sends, so a site believes a non-defaultrole only when the request also carriesAgentEnv-Role-Keyequal to the site'sWEBSITE_ROLE_KEY(compared in constant time). Every other caller, and every caller of a site with no key set, isdefault, soroles=routes are closed until a key is set.index.htmlfallback, the health path with@ready_check, the data plane and a card declaring website/v1 with its routes andopenapi/sessionoperations./agentenv…routes are matched before the site's own.self.now()reads the universe clock (clock/v1sync_time), read once per request from the gateway as MCP servers read it, so a frozen clock is exactly frozen. An unarmed clock is wall time, and an unreachable one answers 503, except on the data plane and the card. An emptyenv_get_time_urlgoes back to wall time. Every response carriesX-Universe-Now;clock_patch=Truepatches the page'sDate.sync_timealso takes main's optionaltimezone(feat(sync_env_clock): an optional timezone reads virtual_time in the zone and tells every synced server (FD-5003) #442), which the site reads asself.timezone;now()stays UTC.database_url), and serves it through the data plane's hooks (@reset_data,@add_data,@get_data) as anyAgentEnvEnvironmentdoes. The SDK has no store of its own. A site's data is read and written only through the data plane, and the SDK serves no/goand nowebsite/v1stateop.agentenv_sessioncookie lasts itsMax-Age(7 days), never an absoluteExpires, so a browser on another clock judges it the same. A deploy gives each site its ownWEBSITE_SESSION_SECRET, so sessions outlive a restart or a snapshot reload; a site run without one (localserve()) draws its own. website/v1'ssession {user?}answers a storage state,{cookies, local_storage}, each cookie withmax_age.def@apihandlers run in a worker thread, as FastAPI's do. A route whose type hints can't be resolved fails the app's build, and session cookies go on successful answers only. The static fallback never serves a dot-named path.website_state_verifier, which feat(website): a website env is one server image, checked at its health path (FD-4683) #390 merged and which read a site's/go?sid=, is removed. Grading a website fromdata/getagainst the task's initial state moves to agentenvhub-sdk'scua_gym_verifier, a separate PR in scaleapi2.python -m agentenv_protocol.website.conformance URLchecks any server over HTTP: card, any host name, health, universe time, data plane, clock, forwarded headers, cookies. It sends nothing to a server other than the one under check, and its clock check syncs the server back to wall time at the end. A cookie with an absoluteExpiresand noMax-Agewarns without failing the run.Breaking
website_state_verifier(from feat(website): a website env is one server image, checked at its health path (FD-4683) #390, released in v0.9.1429–1431) is removed. No stored AgentEnvHub task uses it: 0 of 589,350 synced tasks on Nov 4.Known gaps
Each is fixed later in the stack, as noted.
WEBSITE_ROLE_KEYon a deployed site yet, and the gateway still trustsAgentEnv-Roleon MCP calls, non-SDK sites and website trigger attribution. A per-deploy key in the gateway is the full fix (feat(gateway): a keyed deploy's role key, forwarded headers, and triggers by route (FD-4683) #443, opt-in withkeyed_roles).X-Forwarded-*, so behind it sites must use relative URLs (feat(gateway): a keyed deploy's role key, forwarded headers, and triggers by route (FD-4683) #443).X-Universe-Nowand a filled-in card, stricter than a non-SDK server may be (feat(website): a session cookie per site, the clock's rate, conformance levels and models= (FD-4683) #444 splits required from recommended).Test
packages/agentenv-protocol/tests: 1,115 passed after the session edits (f1bae23); lint clean. Unit suite on CI. Before them: unit suite and protocol tests 12,659 passed.packages/agentenv-protocol/tests: 946 passed; the website tests (59, with the Notes ones) pass after the role-key fix. Deployed locally behind the gateway, a spoofedAgentEnv-Role: adminnow gets 403 on both/website/and/svc/(200 before).🤖 Generated with Claude Code
Update the dependent browser cookie reader before merging the new session shape.
Fix with agent prompt
Summary
Adds
WebsiteEnvironment, typed website routes, sessions, universe time, shared readiness checks, and an HTTP conformance checker.website_state_verifiertask step.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A["website/v1 session"] --> B["Cookie with max_age"] B --> C["browser/v1 cookies/set"] C --> D["Cookie model accepts expires only"] D --> E["invalid_params"]Reviews (16) · Last reviewed commit: "fix(website): sessions last a Max-Age, s..." · Reviewed by Greptile