3 Commits
Author SHA1 Message Date
zeekay cdf0c4c152 A graph's schema is the caller's, and new accounts buy a visitor nothing
The schema a graph of a Base renders named every collection to whoever
asked for it.  It is now as far as the caller reaches: a member sees
the Base whole, a visitor only the collections that admit visitors, and
anyone else only those whose list rule is "".  apis.ReachOf answers the
reach by putting the caller's credential through loadAuthToken, the
door every request passes, so the graph decides nothing of its own.
Each reach keeps its own cached schema, and a root beyond the caller's
reach is no field at all rather than a refusal that says it exists.

A visitor's writes were limited per account, which an account made for
the purpose resets.  They are now also held to fifteen a minute from
one client address and 120 a minute from all visitors in a Base, on
the same limiter.  A write that names no client address is refused.
2026-10-20 20:41:44 -07:00
zeekay 1683110b1c A saved view can be its maker's alone
D12: a person's own views are CRM views rows with visibility private,
which only their maker lists, reads, changes or deletes, an org admin
included; an org view (visibility org, or empty for rows made before)
is every member's to read and its maker's or an org admin's to change.
IAM preferences keep only which view a person opens first.

A Base that installed the CRM earlier holds the old rules, and grow
never touched a rule. A grow document may now name the rules its own
replace (`replaces`): a collection still holding one of those exactly
takes the new rule, and a rule an org wrote is kept, so the next
install tightens the views of every Base that has them.

tests.Router compiles an app's routes for a test that asks several
requests of one app; the graph tests use it.
2026-10-20 07:29:55 -07:00
zeekay ab9da76ea8 A Base's records are a typed GraphQL schema, answered through its own router
graph.New(app, router) renders the Base's collections as GraphQL and
answers every field with a request on the Base's own router, made with
the caller's headers, so the list and view rules, tenancy, rate limits
and expand's view checks are the REST path's and there is no second
authorization. zip's engine executes it: a host mounts g.Schema and
g.Resolve with zip.MountGraphFor, so zip is bumped to v1.37.43.

- A type per base and view collection, named from the collection
  (people: BasePerson, person, people, peopleGroupBy, createPerson,
  updatePerson, upsertPerson, deletePerson), with its fields, relations
  as objects, the records pointing at it as connections
  (opportunities_via_company) and its history.
- A filter input per collection whose operators per field type are the
  where clause's: each is one leaf of core.CompileWhere, so and, or,
  not, @me, relative dates, back-relations and inSegment mean what they
  mean there, every value bound.
- orderBy, cursor connections paged through Base's pages, a totalCount
  asked only when selected, and aggregate and GroupBy from the
  aggregate route.
- Bounds: 100 a page, four relations deep refused before any request,
  50 requests a root field, and zip's 10,000 resolutions.

graph_test drives it through zip against a CRM Base: every filter type,
paging both ways, the figures, the writes as REST reads them back, the
refusals in REST's own words (a rule, a superuser-only collection,
another org's token), the budget, and type names on every object.
tests.IAM signs IAM tokens for any test, and apis' helper uses it.
2026-10-19 09:31:00 -07:00