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>
…ready, its checks bounded, within the time it declares (FD-4683) - WebsiteEnvironment serves no health path, and @website takes no health=; readiness is website/v1's ready alone, as a computer app's is computer-app/v1's. - @ready_check methods run at once, each within CHECK_TIMEOUT_S, a plain def in a worker thread and one call at a time, so a slow check never holds up the site's answers; each problem says what the check returned, raised or how long it ran. - @website(ready_timeout_s=...), 300 s by default, says how long a site may take; ready answers waited_s and timeout_s, and a deploy waits that long and a margin, saying whether a site is past its time. - A server whose card has no ready is still read at its health path, deprecated; conformance needs a health path only then, and its clock check reads /. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bare string was read letter by letter, and a URL, a port, a path or an empty name was kept. @website now takes a list of host names, each like example.com or *.example.com for its subdomains, as host_problem says, the rule live sites' allow_hosts use too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one client session Each request of a TestClient outside a with block runs on a loop thread of its own, so the second request's could reuse the id of the first's ended worker thread. Holding one session keeps the loop's thread alive for both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 data plane and a card declaring website/v1 with its routes andopenapi/ready/sessionoperations.readyalone, as a computer app's is computer-app/v1's:@ready_checkmethods run at once, each within 10 s, a plaindefin a worker thread and one call at a time, each problem saying what the check returned, raised or how long it ran. The answer carrieswaited_sand thetimeout_sfrom@website(ready_timeout_s=...)(300 s by default), which is how long a deploy waits for the site. There is nohealth=path; a server whose card has noreadyis still read at its health path, which is deprecated./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,121 passed after the readiness edits (b5dbda6), with the provider and env unit tests; lint clean. Full unit suite on CI. Before them: unit suite and protocol tests 12,659 passed.End to end on a local Docker gateway (f22dff3): an SDK site sharing the name
slackwith an MCP server, beside the website browser. The deploy waited onready(waited_s21.4,timeout_s120) and never read a health path; the compose carries a 64-hexWEBSITE_SESSION_SECRET, masked in the log. Conformance passed 8/8 through/svc/slack/. Aftersync_env_clocka frozen clock read exactly frozen, and aset-timeshowed on the next answer with no re-sync. The session cookie hasMax-Age=604800and noExpires, and a signed-in cookie still read as its user after a snapshot reload recreated the site's container. The universe'sslackloaded into the MCP server.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


Confidence Score: 5/5
The latest change appears safe to merge; it makes the worker-thread test reliable without changing site behavior.Summary
Adds
WebsiteEnvironmentwith typed API routes, sessions, universe time, readiness checks, and an HTTP conformance checker.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR Browser --> Gateway Gateway -->|Site requests| WebsiteEnvironment Framework -->|Data and control requests| WebsiteEnvironment WebsiteEnvironment --> API[Typed API handlers] WebsiteEnvironment --> Pages[Static pages or mounted app] WebsiteEnvironment --> Clock[Universe clock] Checker[Conformance checker] -->|HTTP checks| WebsiteEnvironmentReviews (19) · Last reviewed commit: "test(website): compare a plain def route..." · Reviewed by Greptile