Plan client/server boundaries
Load plan-feature first. Specialize its design phase with the principle below before freezing an
API, data model, or work breakdown.
Use the established foundation
Apply secure server primitives with client-side composition. Use SSS/CEC — Simple Secure
Server, Composable Ergonomic Client as the Agora shorthand, not as the name of an external
standard. No single established pattern captures the whole rule; it combines these foundations:
- Separate protected mechanism from replaceable product policy. The Exokernel architecture is the
close analogy: a small trusted boundary safely exposes primitives while untrusted application
code supplies replaceable abstractions and policy. Security, legal, and invariant-bearing policy
still belongs to the server.
- Apply the end-to-end argument: keep application-specific behavior at the endpoint that has the
complete requirement unless moving it lower is necessary for correctness or measurably improves
performance.
- Model stable server capabilities as resources plus a small vocabulary of standard or
domain-meaningful methods. Resource-oriented API guidance explicitly allows custom methods when a
transaction or user intent cannot be expressed safely through standard methods.
“Composable” is deliberate: complexity never disappears when moved. Accept product complexity at
the replaceable client edge only when it improves changeability, then contain it in typed
client/domain modules with ergonomic APIs instead of scattering orchestration through visual
components. Optimize total system simplicity and user experience, not server line count alone.
Assign authority before behavior
Keep these responsibilities on the server:
- Derive identity from trusted authentication and authorize every object access independently.
- Validate every untrusted request and enforce security, privacy, quota, billing, ownership, and
resource-consumption limits, including aggregate abuse across a composed workflow. Treat client
validation as UX only.
- Own authoritative facts and invariants. Compute values such as permissions, prices, entitlements,
balances, policy selections, and provider controls from trusted state.
- Expose a small set of meaningful resource or capability operations. Make each write atomic with
respect to the invariant it owns; use idempotency, conditional writes, or version tokens where
retries and concurrency can repeat or race it. Every operation must remain safe when called by a
malicious client in any order, without an assumed benign prelude.
- Keep secrets, privileged dependencies, private data, and audit evidence behind the trust boundary.
- Persist durable state and durable execution. Represent long-running or paid work as an
owner-scoped job with bounded submission, status, cancellation, idempotency, and usage rather than
relying on one browser connection.
Keep these responsibilities in the client when the server duties above remain intact:
- Interpret product definitions, traverse flows, choose ordering and context, compose independent
operations, and decide which proposals or intermediate results to save.
- Own presentation policy, form shape, progressive disclosure, optimistic interaction, conflict UI,
local drafts, and recovery guidance.
- Validate and migrate evolution-heavy client-owned documents, select workflow-specific schemas,
and adapt one stable set of server capabilities into several user journeys.
- Resume orchestration from durable resource and job identifiers after reload, disconnect, or
replacement by another client implementation.
- Provide the ergonomic high-level API in a typed client library when several frontend features need
the same composition. Keep the wire contract primitive and the consuming API pleasant.
Keep the stable envelope; loosen only the payload
Never use “ditch data stability” without qualifying what becomes flexible. Preserve durable bytes or
semantics, ownership, resource identity, version/concurrency tokens, timestamps, size and retention
limits, and stable entry/exit contracts.
Allow opaque or weakly constrained versioned JSON only when its meaning belongs to a replaceable
client workflow and accepting an older or newer shape cannot violate a server invariant. The server
still validates generic type, size, depth, retention, and content-safety limits; the client validates
and migrates its schema. Elevate a shape into a server-owned/public contract when several
independent clients must interpret it identically, it crosses a publication or integration
boundary, or malformed content could affect authorization, money, privacy, or another user's data.
Prefer a stable ownership envelope around evolvable content over either extreme: a rigid server
schema for every UI revision or an unbounded arbitrary-JSON endpoint with no domain boundary.
Use this placement test
Keep behavior on the server when any answer is yes:
- Must several writes succeed or fail together to preserve a business invariant?
- Does the behavior decide access, money, quota, identity, privacy, provider policy, or another
authoritative fact?
- Must it continue safely after every client disconnects, or coordinate concurrent callers?
- Would exposing the required data, secret, or internal topology cross a trust boundary?
- Must independent clients observe one shared interpretation at the same time?
- Would client composition create an unmeasured N+1, high-latency, high-payload, or failure-prone
path?
Move behavior to the client only when all answers are yes:
- Can it be composed from operations that remain independently authorized, valid, invariant-safe,
quota-bounded, and atomic under arbitrary ordering, replay, and concurrency?
- Can every partial result be observed, resumed, retried, compensated, or intentionally abandoned
without corrupting authoritative state?
- Is it product or interaction policy likely to vary by client or redesign?
- Can the client safely receive every input and choose every exposed option?
- Can supported clients and the shared typed library evolve on a compatible schedule, with explicit
payload versions or capability discovery wherever they cannot?
- Is the round-trip, payload, concurrency, battery, and failure budget acceptable under measurement?
- Can one correlation or operation identifier reconstruct the composed journey for support, audit,
and abuse detection?
When only network cost fails the test, add the narrowest bulk read, projection, batch submission, or
gateway aggregation that fixes the measured path. Do not move the whole workflow server-side by
default. When atomicity or trust fails, keep that unit on the server even if the surrounding journey
stays client-orchestrated.
Use this escalation ladder; stop at the first shape that fully meets the proof above:
- Independent server primitives composed by the client.
- A projection, bulk read, or bounded batch for a measured network problem.
- One domain transaction or custom method for an invariant that spans calls.
- A durable server job or saga for work that outlives the client or requires coordinated recovery.
Plan the boundary
- Map the user journey and identify every read, decision, side effect, and durable result.
- Mark the trust source, invariant owner, and atomic commit boundary for each side effect.
- Design the smallest meaningful server capabilities. Avoid both screen-shaped endpoints and raw
table mutation APIs.
- Compose at least two materially different client journeys from the same capabilities. Require a
new server operation only for a new authoritative fact, invariant, protected capability, or
measured performance need.
- Attack the design with a malicious, stale, duplicated, reordered, concurrent, and disconnected
client. Specify idempotency, conflict detection, partial completion, cancellation, and recovery.
- Count network round trips and fan-out on critical journeys. Measure total user-facing latency and
total resource cost; lower server CPU alone is not a performance result.
- State the supported client/update model and trace a composed journey end to end. Version any
client-owned payload; define correlation and operation identifiers across calls.
- Record the decision in the planning issue before breaking work down by repository.
Add this conditional section to the plan-feature issue body:
## Client/server boundary
| Concern | Server mechanism / authority | Client policy / composition | Failure & recovery |
| --------- | --------------------------------- | --------------------------- | ------------------------------------- |
| <concern> | <trusted primitive and invariant> | <ergonomic workflow> | <retry, conflict, partial completion> |
- **Stable envelope:** <identity, ownership, versioning, limits, entry/exit contracts>.
- **Evolvable payload:** <client-owned shape and migration responsibility, or none>.
- **Composition proof:** <arbitrary order/replay/concurrency, partial failure, aggregate limits>.
- **Compatibility:** <supported clients, update cadence, capability/payload versioning>.
- **Total cost budget:** <latency, calls, payload/fan-out, client/server compute, measurement>.
- **Escalation:** <primitives | projection/batch | domain transaction | durable job/saga>.
- **Why this cut:** <how a redesign can change the client without weakening server guarantees>.
Reject these shortcuts
- Trusting client-side validation, identity, totals, policy, or hidden fields.
- Assuming fewer server workflows means less security work; every exposed sequence expands the
adversarial state space unless each primitive closes its own trust and invariant checks.
- Splitting one invariant into a sequence of independently fallible client writes.
- Adding an endpoint per screen, button, Engine, or journey when stable primitives compose it.
- Calling raw table CRUD “simple”; a primitive is domain-meaningful and invariant-safe.
- Freezing an evolution-heavy client document into server logic solely because the current UI knows
its schema.
- Calling arbitrary JSON “flexible” when it can change access, money, public interoperability, or
another user's state.
- Treating opaque JSON as contract-free; flexibility without bounded validation, explicit versions,
and an update model creates interoperability debt.
- Claiming client orchestration improves performance without counting network calls, payloads,
retries, mobile latency, and partial failures.
- Moving long-running execution into a browser lifecycle; keep orchestration policy client-side and
each durable paid or privileged operation server-side.
- Using “smart endpoints and dumb pipes” as a synonym. In microservices literature, the smart
endpoint is usually the service, not the browser client.
Research basis
- Engler, Kaashoek, and O'Toole — Exokernel: separate resource protection from application-level management.
- Saltzer, Reed, and Clark — End-to-End Arguments in System Design: place application-specific functions at the endpoint that can completely implement them.
- Fielding — REST: use stateless interactions and a uniform resource interface for visibility, scalability, and independent evolution.
- Google AIP-121 and AIP-136: prefer stable resources and standard methods, but use a domain custom method when a transaction or user intent does not fit them.
- Saltzer and Schroeder — The Protection of Information in Computer Systems: keep protection mechanisms economical and mediate every access.
- NIST SP 800-207: grant no implicit trust to a caller; authenticate and authorize access to each protected resource.
- OWASP input validation and business-logic security: enforce trust and legal combinations on the server.
- RFC 9110: use idempotent semantics and conditional requests to make retries and concurrent writes safe.
- Google AIP-151, AIP-180, and AIP-231: give long work a durable operation, preserve source/wire/semantic compatibility, and batch when consistent multi-resource reads require it.
- AWS saga orchestration: use durable coordination and compensating actions when a distributed business transaction cannot be one atomic commit.
- Azure chatty I/O and gateway aggregation: count network calls and introduce aggregation only for a measured path.
- RFC 9413: flexibility is not free; explicit extension and error-handling rules are safer than tolerating arbitrary input.
- Model Context Protocol architecture: an independent example of simple capability servers with orchestration in a host; its trusted host is not precedent for trusting a browser client.
1---2name: plan-client-server-boundary3description: Plan responsibility boundaries between browser/app clients and backend services: trusted state, authorization, validation, invariants, atomic operations, workflow orchestration, evolvable data, retries, and performance. Load it with `plan-feature` whenever a feature adds or changes a public API, moves behavior between frontend and backend, composes several service calls, stores client-defined JSON, or introduces a long-running or paid operation.4---56# Plan client/server boundaries78Load `plan-feature` first. Specialize its design phase with the principle below before freezing an9API, data model, or work breakdown.1011## Use the established foundation1213Apply **secure server primitives with client-side composition**. Use **SSS/CEC — Simple Secure14Server, Composable Ergonomic Client** as the Agora shorthand, not as the name of an external15standard. No single established pattern captures the whole rule; it combines these foundations:1617- Separate protected mechanism from replaceable product policy. The Exokernel architecture is the18 close analogy: a small trusted boundary safely exposes primitives while untrusted application19 code supplies replaceable abstractions and policy. Security, legal, and invariant-bearing policy20 still belongs to the server.21- Apply the end-to-end argument: keep application-specific behavior at the endpoint that has the22 complete requirement unless moving it lower is necessary for correctness or measurably improves23 performance.24- Model stable server capabilities as resources plus a small vocabulary of standard or25 domain-meaningful methods. Resource-oriented API guidance explicitly allows custom methods when a26 transaction or user intent cannot be expressed safely through standard methods.2728“Composable” is deliberate: complexity never disappears when moved. Accept product complexity at29the replaceable client edge only when it improves changeability, then contain it in typed30client/domain modules with ergonomic APIs instead of scattering orchestration through visual31components. Optimize total system simplicity and user experience, not server line count alone.3233## Assign authority before behavior3435Keep these responsibilities on the **server**:3637- Derive identity from trusted authentication and authorize every object access independently.38- Validate every untrusted request and enforce security, privacy, quota, billing, ownership, and39 resource-consumption limits, including aggregate abuse across a composed workflow. Treat client40 validation as UX only.41- Own authoritative facts and invariants. Compute values such as permissions, prices, entitlements,42 balances, policy selections, and provider controls from trusted state.43- Expose a small set of meaningful resource or capability operations. Make each write atomic with44 respect to the invariant it owns; use idempotency, conditional writes, or version tokens where45 retries and concurrency can repeat or race it. Every operation must remain safe when called by a46 malicious client in any order, without an assumed benign prelude.47- Keep secrets, privileged dependencies, private data, and audit evidence behind the trust boundary.48- Persist durable state and durable execution. Represent long-running or paid work as an49 owner-scoped job with bounded submission, status, cancellation, idempotency, and usage rather than50 relying on one browser connection.5152Keep these responsibilities in the **client** when the server duties above remain intact:5354- Interpret product definitions, traverse flows, choose ordering and context, compose independent55 operations, and decide which proposals or intermediate results to save.56- Own presentation policy, form shape, progressive disclosure, optimistic interaction, conflict UI,57 local drafts, and recovery guidance.58- Validate and migrate evolution-heavy client-owned documents, select workflow-specific schemas,59 and adapt one stable set of server capabilities into several user journeys.60- Resume orchestration from durable resource and job identifiers after reload, disconnect, or61 replacement by another client implementation.62- Provide the ergonomic high-level API in a typed client library when several frontend features need63 the same composition. Keep the wire contract primitive and the consuming API pleasant.6465## Keep the stable envelope; loosen only the payload6667Never use “ditch data stability” without qualifying what becomes flexible. Preserve durable bytes or68semantics, ownership, resource identity, version/concurrency tokens, timestamps, size and retention69limits, and stable entry/exit contracts.7071Allow opaque or weakly constrained versioned JSON only when its meaning belongs to a replaceable72client workflow and accepting an older or newer shape cannot violate a server invariant. The server73still validates generic type, size, depth, retention, and content-safety limits; the client validates74and migrates its schema. Elevate a shape into a server-owned/public contract when several75independent clients must interpret it identically, it crosses a publication or integration76boundary, or malformed content could affect authorization, money, privacy, or another user's data.7778Prefer a stable ownership envelope around evolvable content over either extreme: a rigid server79schema for every UI revision or an unbounded arbitrary-JSON endpoint with no domain boundary.8081## Use this placement test8283Keep behavior on the server when **any** answer is yes:8485- Must several writes succeed or fail together to preserve a business invariant?86- Does the behavior decide access, money, quota, identity, privacy, provider policy, or another87 authoritative fact?88- Must it continue safely after every client disconnects, or coordinate concurrent callers?89- Would exposing the required data, secret, or internal topology cross a trust boundary?90- Must independent clients observe one shared interpretation at the same time?91- Would client composition create an unmeasured N+1, high-latency, high-payload, or failure-prone92 path?9394Move behavior to the client only when **all** answers are yes:9596- Can it be composed from operations that remain independently authorized, valid, invariant-safe,97 quota-bounded, and atomic under arbitrary ordering, replay, and concurrency?98- Can every partial result be observed, resumed, retried, compensated, or intentionally abandoned99 without corrupting authoritative state?100- Is it product or interaction policy likely to vary by client or redesign?101- Can the client safely receive every input and choose every exposed option?102- Can supported clients and the shared typed library evolve on a compatible schedule, with explicit103 payload versions or capability discovery wherever they cannot?104- Is the round-trip, payload, concurrency, battery, and failure budget acceptable under measurement?105- Can one correlation or operation identifier reconstruct the composed journey for support, audit,106 and abuse detection?107108When only network cost fails the test, add the narrowest bulk read, projection, batch submission, or109gateway aggregation that fixes the measured path. Do not move the whole workflow server-side by110default. When atomicity or trust fails, keep that unit on the server even if the surrounding journey111stays client-orchestrated.112113Use this escalation ladder; stop at the first shape that fully meets the proof above:1141151. Independent server primitives composed by the client.1162. A projection, bulk read, or bounded batch for a measured network problem.1173. One domain transaction or custom method for an invariant that spans calls.1184. A durable server job or saga for work that outlives the client or requires coordinated recovery.119120## Plan the boundary1211221. Map the user journey and identify every read, decision, side effect, and durable result.1232. Mark the trust source, invariant owner, and atomic commit boundary for each side effect.1243. Design the smallest meaningful server capabilities. Avoid both screen-shaped endpoints and raw125 table mutation APIs.1264. Compose at least two materially different client journeys from the same capabilities. Require a127 new server operation only for a new authoritative fact, invariant, protected capability, or128 measured performance need.1295. Attack the design with a malicious, stale, duplicated, reordered, concurrent, and disconnected130 client. Specify idempotency, conflict detection, partial completion, cancellation, and recovery.1316. Count network round trips and fan-out on critical journeys. Measure total user-facing latency and132 total resource cost; lower server CPU alone is not a performance result.1337. State the supported client/update model and trace a composed journey end to end. Version any134 client-owned payload; define correlation and operation identifiers across calls.1358. Record the decision in the planning issue before breaking work down by repository.136137Add this conditional section to the `plan-feature` issue body:138139```markdown140## Client/server boundary141142| Concern | Server mechanism / authority | Client policy / composition | Failure & recovery |143| --------- | --------------------------------- | --------------------------- | ------------------------------------- |144| <concern> | <trusted primitive and invariant> | <ergonomic workflow> | <retry, conflict, partial completion> |145146- **Stable envelope:** <identity, ownership, versioning, limits, entry/exit contracts>.147- **Evolvable payload:** <client-owned shape and migration responsibility, or none>.148- **Composition proof:** <arbitrary order/replay/concurrency, partial failure, aggregate limits>.149- **Compatibility:** <supported clients, update cadence, capability/payload versioning>.150- **Total cost budget:** <latency, calls, payload/fan-out, client/server compute, measurement>.151- **Escalation:** <primitives | projection/batch | domain transaction | durable job/saga>.152- **Why this cut:** <how a redesign can change the client without weakening server guarantees>.153```154155## Reject these shortcuts156157- Trusting client-side validation, identity, totals, policy, or hidden fields.158- Assuming fewer server workflows means less security work; every exposed sequence expands the159 adversarial state space unless each primitive closes its own trust and invariant checks.160- Splitting one invariant into a sequence of independently fallible client writes.161- Adding an endpoint per screen, button, Engine, or journey when stable primitives compose it.162- Calling raw table CRUD “simple”; a primitive is domain-meaningful and invariant-safe.163- Freezing an evolution-heavy client document into server logic solely because the current UI knows164 its schema.165- Calling arbitrary JSON “flexible” when it can change access, money, public interoperability, or166 another user's state.167- Treating opaque JSON as contract-free; flexibility without bounded validation, explicit versions,168 and an update model creates interoperability debt.169- Claiming client orchestration improves performance without counting network calls, payloads,170 retries, mobile latency, and partial failures.171- Moving long-running execution into a browser lifecycle; keep orchestration policy client-side and172 each durable paid or privileged operation server-side.173- Using “smart endpoints and dumb pipes” as a synonym. In microservices literature, the smart174 endpoint is usually the service, not the browser client.175176## Research basis177178- [Engler, Kaashoek, and O'Toole — Exokernel](https://pdos.csail.mit.edu/6.1810/2017/readings/engler95exokernel.pdf): separate resource protection from application-level management.179- [Saltzer, Reed, and Clark — End-to-End Arguments in System Design](https://doi.org/10.1145/357401.357402): place application-specific functions at the endpoint that can completely implement them.180- [Fielding — REST](https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm): use stateless interactions and a uniform resource interface for visibility, scalability, and independent evolution.181- [Google AIP-121](https://google.aip.dev/121) and [AIP-136](https://google.aip.dev/136): prefer stable resources and standard methods, but use a domain custom method when a transaction or user intent does not fit them.182- [Saltzer and Schroeder — The Protection of Information in Computer Systems](https://www.cs.virginia.edu/~evans/cs551/saltzer/): keep protection mechanisms economical and mediate every access.183- [NIST SP 800-207](https://csrc.nist.gov/pubs/sp/800/207/final): grant no implicit trust to a caller; authenticate and authorize access to each protected resource.184- [OWASP input validation](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) and [business-logic security](https://cheatsheetseries.owasp.org/cheatsheets/Business_Logic_Security_Cheat_Sheet.html): enforce trust and legal combinations on the server.185- [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html): use idempotent semantics and conditional requests to make retries and concurrent writes safe.186- [Google AIP-151](https://google.aip.dev/151), [AIP-180](https://google.aip.dev/180), and [AIP-231](https://google.aip.dev/231): give long work a durable operation, preserve source/wire/semantic compatibility, and batch when consistent multi-resource reads require it.187- [AWS saga orchestration](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/saga-orchestration.html): use durable coordination and compensating actions when a distributed business transaction cannot be one atomic commit.188- [Azure chatty I/O](https://learn.microsoft.com/en-us/azure/architecture/antipatterns/chatty-io/) and [gateway aggregation](https://learn.microsoft.com/en-us/azure/architecture/patterns/gateway-aggregation): count network calls and introduce aggregation only for a measured path.189- [RFC 9413](https://www.rfc-editor.org/rfc/rfc9413.html): flexibility is not free; explicit extension and error-handling rules are safer than tolerating arbitrary input.190- [Model Context Protocol architecture](https://modelcontextprotocol.io/specification/2025-06-18/architecture/index): an independent example of simple capability servers with orchestration in a host; its trusted host is not precedent for trusting a browser client.