Skip to content

feat(website): one spec declares a site, composes it and checks any server (FD-4683) - #445

Open
williamloo-scale wants to merge 28 commits into
wloo/website-sdk-followupsfrom
wloo/website-spec
Open

williamloo-scale wants to merge 28 commits into
wloo/website-sdk-followupsfrom
wloo/website-spec

Conversation

@williamloo-scale

@williamloo-scale williamloo-scale commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #444.

A website spec: one TOML file declares a site, composes from parts, and checks any server against it. Design: docs/website-spec.md.

  • agentenv_protocol.website.spec reads [site], [source], [data], [users], [roles], [routes.ops], [state], [clock], [[uses]] and extends, with validation, overlays, composition, card params, from_card, from_environment, a JSON Schema and a CLI (validate, show, show --card).
  • A name table for a multi env: synthetic over live over the allowlist, two sites of one kind on a name refused.
  • @website(spec="website.toml"): the class is refused when its @api routes aren't the spec's, and its card is built from the spec. Notes now declares itself this way.
  • env.toml takes spec = "website.toml" or an inline [spec]; the env takes its name, health path and domains from it.
  • conformance --spec checks a server's card and routes against the spec; those checks are required.
  • Examples in examples/website/specs/: a replayed Slack overlay, an intranet, and an SSO component.

Live and replay sources, served_by and models = "mcp:..." are modelled in the spec but not deployable from env.toml yet.

Test

Unit suite: 12,793 passed (one GitHub-clone test from main, which hangs on the local machine on main too, deselected).

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge, with a non-blocking error-reporting issue for malformed specs.

Fix All in CursorFindings

  1. P2 Mistyped specs crash validation ▶
Fix with agent prompt
### Issue 1
packages/agentenv-protocol/src/agentenv_protocol/website/spec/__init__.py:577-580
A file containing `extends = "base.toml"` and `uses = 1` reaches this loop before `_validated()` and raises `TypeError`. An inherited `data.collections = [1]` in another folder similarly crashes `_rebased()` at `.values()`.

The CLI and bundle authoring catch only `SpecError`, so authors get a traceback instead of an error naming the bad field. Check these types before merging and rebasing, or turn these failures into `SpecError`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds one website spec for declarations, overlays, components, cards, bundle authoring, and conformance checks.

  • A website TOML file now defines and composes a site's contract.
  • Website SDK classes can now take their site contract from a spec.
  • Website environments can now take their name and domains from a spec.
  • Conformance checks can now compare a server with a website spec.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  TOML[Website spec] --> Resolve[Merge overlays and components]
  Resolve --> Spec[Resolved spec]
  Spec --> SDK[Check SDK class]
  SDK --> Card[Serve website card]
  Spec --> Bundle[Read env name, health and domains]
  Spec --> Check[Check any server]
  Card --> Check
Loading

Reviews (7) · Last reviewed commit: "Merge wloo/website-sdk-followups into wl..." · Reviewed by Greptile

williamloo-scale and others added 3 commits November 2, 2026 10:48
…and checks any server (FD-4683)

agentenv_protocol.website.spec models a site as one TOML declaration ([site],
[source], [data], [users], [roles], [routes], [state], [clock], [[uses]],
extends) with validation, extends overlays, uses composition, card params,
from_card, from_environment, a multi env name table and a JSON Schema and CLI.
WebsiteEnvironment can be declared with @website(spec=...) and builds its
website/v1 card through the spec; conformance takes --spec. Notes now declares
itself from website.toml; examples/website/specs shows a replayed Slack overlay
and an SSO component.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
env.toml takes spec = "website.toml" or an inline [spec] table: the env
takes its environment_name and health path from the spec when the env.toml
leaves them out, refuses one that differs, stores the spec's domains, and
refuses a live or replay source, which a server image can't serve.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@williamloo-scale williamloo-scale changed the title website: one spec declares a site, composes it and checks any server (FD-4683) feat(website): one spec declares a site, composes it and checks any server (FD-4683) Nov 3, 2026
@williamloo-scale
williamloo-scale marked this pull request as ready for review November 3, 2026 00:24
@williamloo-scale
williamloo-scale requested a review from a team as a code owner November 3, 2026 00:24
williamloo-scale and others added 4 commits November 2, 2026 16:36
# Conflicts:
#	packages/agentenv-protocol/src/agentenv_protocol/website/conformance.py
#	src/agent_env/env/envs/website.py
…as no website/v1 ready (FD-4683)

A deploy and the conformance check read readiness from the card, so a
spec whose health path differs from the card's is the spec check's to
report; the health check passes on ready.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s session as declared (FD-4683)

to_dict() and overlays keep a JSON Schema as it is, a mounted component's
schema file stays its own, a class's acting_user is checked against the
current_user its seeds leave in load order, the spec check compares the
card's session_path and requires a declared state and clock, spec_routes
fails an advertised OpenAPI document that can't be read, the JSON Schema
takes the false that removes an inherited entry, and a spec is read with
tomli where tomllib is missing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in #428's and #443's review fixes beside this branch's spec
checks: conformance runs the spec checks with the others and checks
X-Universe-Now last; a card now names a route by the path it is served
at, converter included, so the spec check compares paths with any
converter dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…es, its spec or not (FD-4683)

A card built from the site's spec named a route by the spec's template
(/files/{rest}), so the gateway matched one segment of a nested route
and its triggers missed /files/a/b. website/v1's params are still the
spec's, but each route's path is now the one the class serves it at,
converters and all; a spec read back from the card keeps its template.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
williamloo-scale and others added 2 commits November 2, 2026 17:55
…claims, and its check probes typed parameters (FD-4683)

spec_routes probes a path parameter with a value the converter on the
card's route takes (a UUID for uuid, else 0), so a working
/items/{id:uuid} is no longer reported missing. false now removes an
OpenAPI operation through extends and overlay: a removed route stays a
false through the merge, and a resolved spec no longer keeps
routes.openapi, its operations being in routes.ops. An OpenAPI document
of the wrong shape or naming two operations alike is a SpecError, the
name table refuses two sites claiming one pattern at a kind that lost
it, and the JSON Schema takes an overlay's partial route table.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
williamloo-scale and others added 4 commits November 2, 2026 18:29
…tion (FD-4683)

A routes.ops table naming an operation of the routes.openapi document, such as
add_item = { roles = ["staff"] } in an extends file, is now written over the
document's operation before the spec is validated, instead of failing for a
missing method and path. Changing its method or path is still refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… shows it (FD-4683)

For a card that offers no OpenAPI document, a route only the OPTIONS probe can
check (a non-GET route, or a GET route with path parameters) is now served only
when that OPTIONS answers below 500 with an Allow listing its method. A
catch-all's 200 without Allow, a 500, or an Allow that doesn't list the method
leaves the route unverified, which makes the check a warning (a failure with
--strict) rather than a pass; a 404 still fails it. An Allow that omits the
method isn't taken as proof it's missing, because Starlette lists only the
first matching route's methods when several routes share a path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	packages/agentenv-protocol/src/agentenv_protocol/website/environment.py
williamloo-scale and others added 2 commits November 3, 2026 08:31
…y does (FD-4683)

A resolved spec keeps its declaration as written, extends merged but uses not
yet mounted, and overlay() merges into that, so a [[uses]] of the same as
replaces the mounted component instead of clashing with its routes. Equality
still compares only the resolved site.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…its context (FD-4683)

Spec paths accept every character RFC 3986 allows in a path (sub-delims, '@',
percent-escapes), so a WebsiteEnvironment serving /api/users/@me derives a spec
and starts as before. An image source's default context is rebased with its
Dockerfile when an overlay in another folder inherits it, the same as an
explicit context = ".".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
williamloo-scale and others added 3 commits November 3, 2026 09:28
…(FD-4683)

The package supports Python 3.10, which has no tomllib; the spec loader already
falls back to tomli, so it is now a dependency there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
williamloo-scale and others added 5 commits November 3, 2026 10:22
…and may act as a component's persona (FD-4683)

A site's acting_user is now checked only after its components' personas are merged, so it may be one only a
component declares. A component route mounted under a prefix keeps its trailing slash: /items/ under /auth is
/auth/items/ and / is /auth/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +577 to +580
elif key == "uses" and not at:
kept = {_use_id(u): u for u in merged.get("uses", [])}
kept.update({_use_id(u): copy.deepcopy(u) for u in value})
merged["uses"] = list(kept.values())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Mistyped specs crash validation

A file containing extends = "base.toml" and uses = 1 reaches this loop before _validated() and raises TypeError. An inherited data.collections = [1] in another folder similarly crashes _rebased() at .values().

The CLI and bundle authoring catch only SpecError, so authors get a traceback instead of an error naming the bad field. Check these types before merging and rebasing, or turn these failures into SpecError.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agentenv-protocol/src/agentenv_protocol/website/spec/__init__.py
Line: 577-580

Comment:
**Mistyped specs crash validation**

A file containing `extends = "base.toml"` and `uses = 1` reaches this loop before `_validated()` and raises `TypeError`. An inherited `data.collections = [1]` in another folder similarly crashes `_rebased()` at `.values()`.

The CLI and bundle authoring catch only `SpecError`, so authors get a traceback instead of an error naming the bad field. Check these types before merging and rebasing, or turn these failures into `SpecError`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant