All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m33s
Add boundary.sx declaring all 34 I/O primitives, 32 page helpers, and 9 allowed boundary types. Runtime validation in boundary.py checks every registration against the spec — undeclared primitives/helpers crash at startup with SX_BOUNDARY_STRICT=1 (now set in both dev and prod). Key changes: - Move 5 I/O-in-disguise primitives (app-url, asset-url, config, jinja-global, relations-from) from primitives.py to primitives_io.py - Remove duplicate url-for/route-prefix from primitives.py (already in IO) - Fix parse-datetime to return ISO string instead of raw datetime - Add datetime→isoformat conversion in _convert_result at the edge - Wrap page helper return values with boundary type validation - Replace all SxExpr(f"...") patterns with sx_call() or _sx_fragment() - Add assert declaration to primitives.sx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
457 lines
11 KiB
Plaintext
457 lines
11 KiB
Plaintext
;; ==========================================================================
|
|
;; boundary.sx — SX boundary contract
|
|
;;
|
|
;; Declares everything allowed to cross the host-SX boundary:
|
|
;; I/O primitives (Tier 2) and page helpers (Tier 3).
|
|
;;
|
|
;; Pure primitives (Tier 1) are declared in primitives.sx.
|
|
;; This file declares what primitives.sx does NOT cover:
|
|
;; async/side-effectful host functions that need request context.
|
|
;;
|
|
;; Format:
|
|
;; (define-io-primitive "name"
|
|
;; :params (param1 param2 &key ...)
|
|
;; :returns "type"
|
|
;; :async true
|
|
;; :doc "description"
|
|
;; :context :request)
|
|
;;
|
|
;; (define-page-helper "name"
|
|
;; :params (param1 param2)
|
|
;; :returns "type"
|
|
;; :service "service-name")
|
|
;;
|
|
;; Bootstrappers read this file and emit frozen sets + validation
|
|
;; functions for the target language.
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Tier 1: Pure primitives — declared in primitives.sx
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(declare-tier :pure :source "primitives.sx")
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Tier 2: I/O primitives — async, side-effectful, need host context
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define-io-primitive "frag"
|
|
:params (service frag-type &key)
|
|
:returns "string"
|
|
:async true
|
|
:doc "Fetch cross-service HTML fragment."
|
|
:context :request)
|
|
|
|
(define-io-primitive "query"
|
|
:params (service query-name &key)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Fetch data from another service via internal HTTP."
|
|
:context :request)
|
|
|
|
(define-io-primitive "action"
|
|
:params (service action-name &key)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Call an action on another service via internal HTTP."
|
|
:context :request)
|
|
|
|
(define-io-primitive "current-user"
|
|
:params ()
|
|
:returns "dict?"
|
|
:async true
|
|
:doc "Current authenticated user dict, or nil."
|
|
:context :request)
|
|
|
|
(define-io-primitive "htmx-request?"
|
|
:params ()
|
|
:returns "boolean"
|
|
:async true
|
|
:doc "True if current request has HX-Request header."
|
|
:context :request)
|
|
|
|
(define-io-primitive "service"
|
|
:params (service-or-method &rest args &key)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Call a domain service method. Two-arg: (service svc method). One-arg: (service method) uses bound handler service."
|
|
:context :request)
|
|
|
|
(define-io-primitive "request-arg"
|
|
:params (name &rest default)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Read a query string argument from the current request."
|
|
:context :request)
|
|
|
|
(define-io-primitive "request-path"
|
|
:params ()
|
|
:returns "string"
|
|
:async true
|
|
:doc "Current request path."
|
|
:context :request)
|
|
|
|
(define-io-primitive "nav-tree"
|
|
:params ()
|
|
:returns "list"
|
|
:async true
|
|
:doc "Navigation tree as list of node dicts."
|
|
:context :request)
|
|
|
|
(define-io-primitive "get-children"
|
|
:params (&key parent-type parent-id)
|
|
:returns "list"
|
|
:async true
|
|
:doc "Fetch child entities for a parent."
|
|
:context :request)
|
|
|
|
(define-io-primitive "g"
|
|
:params (key)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Read a value from the Quart request-local g object."
|
|
:context :request)
|
|
|
|
(define-io-primitive "csrf-token"
|
|
:params ()
|
|
:returns "string"
|
|
:async true
|
|
:doc "Current CSRF token string."
|
|
:context :request)
|
|
|
|
(define-io-primitive "abort"
|
|
:params (status &rest message)
|
|
:returns "nil"
|
|
:async true
|
|
:doc "Raise HTTP error from SX."
|
|
:context :request)
|
|
|
|
(define-io-primitive "url-for"
|
|
:params (endpoint &key)
|
|
:returns "string"
|
|
:async true
|
|
:doc "Generate URL for a named endpoint."
|
|
:context :request)
|
|
|
|
(define-io-primitive "route-prefix"
|
|
:params ()
|
|
:returns "string"
|
|
:async true
|
|
:doc "Service URL prefix for dev/prod routing."
|
|
:context :request)
|
|
|
|
(define-io-primitive "root-header-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with root header values (cart-mini, auth-menu, nav-tree, etc.)."
|
|
:context :request)
|
|
|
|
(define-io-primitive "post-header-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with post-level header values."
|
|
:context :request)
|
|
|
|
(define-io-primitive "select-colours"
|
|
:params ()
|
|
:returns "string"
|
|
:async true
|
|
:doc "Shared select/hover CSS class string."
|
|
:context :request)
|
|
|
|
(define-io-primitive "account-nav-ctx"
|
|
:params ()
|
|
:returns "any"
|
|
:async true
|
|
:doc "Account nav fragments, or nil."
|
|
:context :request)
|
|
|
|
(define-io-primitive "app-rights"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "User rights dict from g.rights."
|
|
:context :request)
|
|
|
|
(define-io-primitive "federation-actor-ctx"
|
|
:params ()
|
|
:returns "dict?"
|
|
:async true
|
|
:doc "Serialized ActivityPub actor dict or nil."
|
|
:context :request)
|
|
|
|
(define-io-primitive "request-view-args"
|
|
:params (key)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Read a URL view argument from the current request."
|
|
:context :request)
|
|
|
|
(define-io-primitive "cart-page-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with cart page header values."
|
|
:context :request)
|
|
|
|
(define-io-primitive "events-calendar-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with events calendar header values."
|
|
:context :request)
|
|
|
|
(define-io-primitive "events-day-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with events day header values."
|
|
:context :request)
|
|
|
|
(define-io-primitive "events-entry-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with events entry header values."
|
|
:context :request)
|
|
|
|
(define-io-primitive "events-slot-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with events slot header values."
|
|
:context :request)
|
|
|
|
(define-io-primitive "events-ticket-type-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with ticket type header values."
|
|
:context :request)
|
|
|
|
(define-io-primitive "market-header-ctx"
|
|
:params ()
|
|
:returns "dict"
|
|
:async true
|
|
:doc "Dict with market header data."
|
|
:context :request)
|
|
|
|
;; Moved from primitives.py — these need host context (infra/config/Quart)
|
|
|
|
(define-io-primitive "app-url"
|
|
:params (service &rest path)
|
|
:returns "string"
|
|
:async false
|
|
:doc "Full URL for a service: (app-url \"blog\" \"/my-post/\")."
|
|
:context :config)
|
|
|
|
(define-io-primitive "asset-url"
|
|
:params (&rest path)
|
|
:returns "string"
|
|
:async false
|
|
:doc "Versioned static asset URL."
|
|
:context :config)
|
|
|
|
(define-io-primitive "config"
|
|
:params (key)
|
|
:returns "any"
|
|
:async false
|
|
:doc "Read a value from app-config.yaml."
|
|
:context :config)
|
|
|
|
(define-io-primitive "jinja-global"
|
|
:params (key &rest default)
|
|
:returns "any"
|
|
:async false
|
|
:doc "Read a Jinja environment global."
|
|
:context :request)
|
|
|
|
(define-io-primitive "relations-from"
|
|
:params (entity-type)
|
|
:returns "list"
|
|
:async false
|
|
:doc "List of RelationDef dicts for an entity type."
|
|
:context :config)
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Tier 3: Page helpers — service-scoped, registered per app
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; SX docs service
|
|
(define-page-helper "highlight"
|
|
:params (code lang)
|
|
:returns "sx-source"
|
|
:service "sx")
|
|
|
|
(define-page-helper "primitives-data"
|
|
:params ()
|
|
:returns "dict"
|
|
:service "sx")
|
|
|
|
(define-page-helper "reference-data"
|
|
:params (slug)
|
|
:returns "dict"
|
|
:service "sx")
|
|
|
|
(define-page-helper "attr-detail-data"
|
|
:params (slug)
|
|
:returns "dict"
|
|
:service "sx")
|
|
|
|
(define-page-helper "header-detail-data"
|
|
:params (slug)
|
|
:returns "dict"
|
|
:service "sx")
|
|
|
|
(define-page-helper "event-detail-data"
|
|
:params (slug)
|
|
:returns "dict"
|
|
:service "sx")
|
|
|
|
(define-page-helper "read-spec-file"
|
|
:params (filename)
|
|
:returns "string"
|
|
:service "sx")
|
|
|
|
(define-page-helper "bootstrapper-data"
|
|
:params (target)
|
|
:returns "dict"
|
|
:service "sx")
|
|
|
|
;; Blog service
|
|
(define-page-helper "editor-data"
|
|
:params (&key)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
(define-page-helper "editor-page-data"
|
|
:params (&key)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
(define-page-helper "post-admin-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
(define-page-helper "post-data-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
(define-page-helper "post-preview-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
(define-page-helper "post-entries-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
(define-page-helper "post-settings-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
(define-page-helper "post-edit-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "blog")
|
|
|
|
;; Events service
|
|
(define-page-helper "calendar-admin-data"
|
|
:params (&key calendar-slug)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "day-admin-data"
|
|
:params (&key calendar-slug year month day)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "slots-data"
|
|
:params (&key calendar-slug)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "slot-data"
|
|
:params (&key calendar-slug slot-id)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "entry-data"
|
|
:params (&key calendar-slug entry-id)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "entry-admin-data"
|
|
:params (&key calendar-slug entry-id year month day)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "ticket-types-data"
|
|
:params (&key calendar-slug entry-id year month day)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "ticket-type-data"
|
|
:params (&key calendar-slug entry-id ticket-type-id year month day)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "tickets-data"
|
|
:params (&key)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "ticket-detail-data"
|
|
:params (&key code)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "ticket-admin-data"
|
|
:params (&key)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
(define-page-helper "markets-data"
|
|
:params (&key)
|
|
:returns "dict"
|
|
:service "events")
|
|
|
|
;; Market service
|
|
(define-page-helper "all-markets-data"
|
|
:params (&key)
|
|
:returns "dict"
|
|
:service "market")
|
|
|
|
(define-page-helper "page-markets-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "market")
|
|
|
|
(define-page-helper "page-admin-data"
|
|
:params (&key slug)
|
|
:returns "dict"
|
|
:service "market")
|
|
|
|
(define-page-helper "market-home-data"
|
|
:params (&key page-slug market-slug)
|
|
:returns "dict"
|
|
:service "market")
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Boundary types — what's allowed to cross the host-SX boundary
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define-boundary-types
|
|
(list "number" "string" "boolean" "nil" "keyword"
|
|
"list" "dict" "sx-source" "style-value"))
|