Module: Tep
- Defined in:
- lib/tep/app.rb,
lib/tep.rb,
lib/tep/job.rb,
lib/tep/jwt.rb,
lib/tep/llm.rb,
lib/tep/mcp.rb,
lib/tep/url.rb,
lib/tep/auth.rb,
lib/tep/http.rb,
lib/tep/json.rb,
lib/tep/cache.rb,
lib/tep/proxy.rb,
lib/tep/shell.rb,
lib/tep/assets.rb,
lib/tep/events.rb,
lib/tep/filter.rb,
lib/tep/logger.rb,
lib/tep/parser.rb,
lib/tep/router.rb,
lib/tep/server.rb,
lib/tep/sqlite.rb,
lib/tep/handler.rb,
lib/tep/request.rb,
lib/tep/session.rb,
lib/tep/version.rb,
lib/tep/identity.rb,
lib/tep/parallel.rb,
lib/tep/password.rb,
lib/tep/presence.rb,
lib/tep/response.rb,
lib/tep/security.rb,
lib/tep/streamer.rb,
lib/tep/broadcast.rb,
lib/tep/live_view.rb,
lib/tep/multipart.rb,
lib/tep/scheduler.rb,
lib/tep/websocket.rb,
lib/tep/auth_oauth2.rb,
lib/tep/openai_server.rb,
lib/tep/presence_entry.rb,
lib/tep/websocket/frame.rb,
lib/tep/agent_delegation.rb,
lib/tep/auth_oauth2_code.rb,
lib/tep/server_scheduled.rb,
lib/tep/websocket/driver.rb,
lib/tep/auth_bearer_token.rb,
lib/tep/auth_oauth2_client.rb,
lib/tep/auth_session_cookie.rb,
lib/tep/websocket/handshake.rb,
lib/tep/websocket/connection.rb,
lib/tep/broadcast_subscription.rb
Overview
Tep::BroadcastSubscription – one entry in the Tep::Broadcast subscriber registry. Pairs a topic name with an output fd. When a publish matches the topic, the fd gets the payload bytes via Sock.sphttp_write_str.
fd is just an integer file descriptor: typically a WebSocket connection’s accepted socket fd, but the registry doesn’t care about the protocol on top – it’ll write to any open fd. Apps integrating with WS (via Tep::WebSocket) subscribe their connection fds; non-WS use cases (server-sent events, log fan-out, etc.) work the same way.
Each subscription lives in a single worker’s registry. Cross- worker pub-sub goes through PG LISTEN/NOTIFY (see Tep::Broadcast.enable_pg_backend) which fans publishes out without moving subscription state; subscribers always register fd-local. See docs/BATTERIES-DESIGN.md for the broader Broadcast battery design.
Defined Under Namespace
Modules: Auth, AuthOAuth2, Broadcast, Cache, MCP, Multipart, Presence, Security, WebSocket Classes: AgentDelegation, App, Assets, AuthBearerToken, AuthFilter, AuthOAuth2Client, AuthOAuth2Code, AuthSessionCookie, BroadcastSubscription, Events, FiberSlot, Filter, Handler, Http, Identity, Job, Json, Jwt, LiveView, Llm, Logger, Parallel, ParallelWorker, Parser, Password, PresenceEntry, Proxy, Request, Response, Route, Router, SQLite, Scheduler, Server, Session, Shell, Stream, Streamer, Url
Constant Summary collapse
- APP =
Session signing secret. Empty by default, which disables session writes (the Set-Cookie path no-ops). Set at app load time:
Tep.session_secret = ENV.fetch("TEP_SESSION_SECRET")Stored on the APP instance (spinel doesn’t reliably type-track module-level ‘@@cvars` or globals).
App.new
- COOKIE_NAME =
"tep.session"- VERSION =
"0.11.1"
Class Method Summary collapse
- .after(filter) ⇒ Object
- .before(filter) ⇒ Object
- .delete(pattern, handler) ⇒ Object
-
.get(pattern, handler) ⇒ Object
—————- DSL —————- Spinel emits every defined method whether called or not, and infers parameter types from concrete call sites; methods nobody calls fall back to int parameters that mismatch the typed ivars they assign.
-
.h(s) ⇒ Object
HTML-escape: minimum safe set for attribute and PCDATA contexts.
- .not_found(handler) ⇒ Object
-
.on_shutdown ⇒ Object
Called by the SERVER PARENT (workers>1) or the single process (workers=1) at SIGTERM/SIGINT, AFTER the worker children have exited.
- .patch(pattern, handler) ⇒ Object
- .post(pattern, handler) ⇒ Object
- .public_dir(root) ⇒ Object
- .put(pattern, handler) ⇒ Object
- .reason(status) ⇒ Object
-
.run!(port, workers, quiet, scheduled = false) ⇒ Object
ARGV access only emits ‘sp_argv` when used at top level, so the translator emits the option-parsing loop itself before calling `Tep.run!`.
-
.seed_fiber ⇒ Object
A canonical no-op fiber, used to type-seed Fiber-bearing collections without running anything user-visible.
- .seed_fiber_noop ⇒ Object
- .session_secret ⇒ Object
- .session_secret=(v) ⇒ Object
-
.str_find(s, needle, start) ⇒ Object
str_find – naive substring search returning the int position of ‘needle` in `s` starting from `start`, or -1 if not found.
- .str_hash ⇒ Object
-
.timing_safe_eq(a, b) ⇒ Object
Constant-time string equality.
-
.tls_cert ⇒ Object
Inbound TLS (tep#148 phase 2).
- .tls_cert=(v) ⇒ Object
- .tls_key ⇒ Object
- .tls_key=(v) ⇒ Object
Class Method Details
.after(filter) ⇒ Object
927 928 929 |
# File 'lib/tep.rb', line 927 def self.after(filter) APP.set_after(filter) end |
.before(filter) ⇒ Object
923 924 925 |
# File 'lib/tep.rb', line 923 def self.before(filter) APP.set_before(filter) end |
.delete(pattern, handler) ⇒ Object
916 |
# File 'lib/tep.rb', line 916 def self.delete(pattern, handler); APP.add_route("DELETE", pattern, handler); end |
.get(pattern, handler) ⇒ Object
—————- DSL —————- Spinel emits every defined method whether called or not, and infers parameter types from concrete call sites; methods nobody calls fall back to int parameters that mismatch the typed ivars they assign. So the v0.1 surface only exposes what the bundled demos actually use; richer DSL methods (before/after/not_found) are layered on as the demos grow to exercise them.
912 |
# File 'lib/tep.rb', line 912 def self.get(pattern, handler); APP.add_route("GET", pattern, handler); end |
.h(s) ⇒ Object
HTML-escape: minimum safe set for attribute and PCDATA contexts. Used by the build-time Mustache compiler for the default ‘{var}` (escaped) form. Char-by-char to avoid `gsub` (spinel’s gsub coverage on string-typed receivers is uneven).
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/tep.rb', line 159 def self.h(s) out = "" i = 0 n = s.length while i < n c = s[i] if c == "&" out = out + "&" elsif c == "<" out = out + "<" elsif c == ">" out = out + ">" elsif c == "\"" out = out + """ elsif c == "'" out = out + "'" else out = out + c end i += 1 end out end |
.not_found(handler) ⇒ Object
931 932 933 |
# File 'lib/tep.rb', line 931 def self.not_found(handler) APP.set_not_found(handler) end |
.on_shutdown ⇒ Object
Called by the SERVER PARENT (workers>1) or the single process (workers=1) at SIGTERM/SIGINT, AFTER the worker children have exited. Children no longer emit run_end themselves – #128 moved the emission here so a multi-worker deployment writes exactly ONE run_end with aggregated stats from the events.jsonl, not N per worker.
reason: “completed” – matches toy/v1 vocabulary (was “ok”; #115). Cheap when nothing is configured: openai_events is seeded with an empty path, whose enabled? short-circuits.
975 976 977 978 979 980 |
# File 'lib/tep.rb', line 975 def self.on_shutdown if APP.openai_events.enabled? APP.openai_events.run_end_aggregated("completed") end 0 end |
.patch(pattern, handler) ⇒ Object
915 |
# File 'lib/tep.rb', line 915 def self.patch(pattern, handler); APP.add_route("PATCH", pattern, handler); end |
.post(pattern, handler) ⇒ Object
913 |
# File 'lib/tep.rb', line 913 def self.post(pattern, handler); APP.add_route("POST", pattern, handler); end |
.public_dir(root) ⇒ Object
919 920 921 |
# File 'lib/tep.rb', line 919 def self.public_dir(root) APP.set_static_root(root) end |
.put(pattern, handler) ⇒ Object
914 |
# File 'lib/tep.rb', line 914 def self.put(pattern, handler); APP.add_route("PUT", pattern, handler); end |
.reason(status) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# File 'lib/tep/server.rb', line 5 def self.reason(status) if status == 200; return "OK"; end if status == 201; return "Created"; end if status == 204; return "No Content"; end if status == 301; return "Moved Permanently"; end if status == 302; return "Found"; end if status == 304; return "Not Modified"; end if status == 400; return "Bad Request"; end if status == 401; return "Unauthorized"; end if status == 403; return "Forbidden"; end if status == 404; return "Not Found"; end if status == 500; return "Internal Server Error"; end "OK" end |
.run!(port, workers, quiet, scheduled = false) ⇒ Object
ARGV access only emits ‘sp_argv` when used at top level, so the translator emits the option-parsing loop itself before calling `Tep.run!`. The `scheduled` flag picks between the prefork blocking server (default) and the fiber-per-connection Tep::Server::Scheduled (opt-in via `set :scheduler, :scheduled` in the app source, or `-s` on the CLI). At the next major tep release Scheduled becomes the default and Blocking is deleted; the parallel-classes period exists only to make the rollback path obvious during the transition.
Single dispatch method (rather than parallel run! / run_scheduled!) because spinel’s codegen mis-declares heap-cell parameters when two same-arity sibling methods are called from an if/else – both branches reference ‘quiet` as a heap-cell but only the first path declares it. Bundling the decision inside one method sidesteps the codegen miss.
‘scheduled` defaults to false so apps that ship the historical 3-arg call (Tep.run!(port, workers, quiet)) keep building. Spinel accepts the call without the 4th arg only because it supports default-value params; without this, the 3-arg call silently miscompiled (matz/spinel arity-warning shape, tep#13).
957 958 959 960 961 962 963 |
# File 'lib/tep.rb', line 957 def self.run!(port, workers, quiet, scheduled = false) if scheduled Server::Scheduled.new(APP).run(port, workers, quiet) else Server.new(APP).run(port, workers, quiet) end end |
.seed_fiber ⇒ Object
A canonical no-op fiber, used to type-seed Fiber-bearing collections without running anything user-visible. The body is a single method call (Fiber tests don’t currently support arbitrary inline-block bodies in spinel).
117 118 119 |
# File 'lib/tep.rb', line 117 def self.seed_fiber Fiber.new { Tep.seed_fiber_noop } end |
.seed_fiber_noop ⇒ Object
109 110 111 |
# File 'lib/tep.rb', line 109 def self.seed_fiber_noop 0 end |
.session_secret ⇒ Object
193 |
# File 'lib/tep.rb', line 193 def self.session_secret; APP.session_secret; end |
.session_secret=(v) ⇒ Object
194 |
# File 'lib/tep.rb', line 194 def self.session_secret=(v); APP.set_session_secret(v); end |
.str_find(s, needle, start) ⇒ Object
str_find – naive substring search returning the int position of ‘needle` in `s` starting from `start`, or -1 if not found.
History: workaround for spinel ‘0210389` which made `String#index` return nil for not-found (was -1). spinel `28545ff` (matz/spinel#550) added int|nil narrowing after an explicit nil-guard, so the nil-side risk is upstream-resolved AND spinel supports the offset overload `s.index(needle, start)` directly (emits `sp_str_index_from_poly`). The helper stays solely for callsite ergonomics: the 17 callers all use `if x < 0` style int comparison (which can’t narrow against int|nil under spinel’s current narrowing model). Removing it would require a mechanical ‘< 0` -> `.nil?` refactor across http.rb / parser.rb / url.rb / jwt.rb / app.rb. Worth doing eventually; not urgent.
142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/tep.rb', line 142 def self.str_find(s, needle, start) nlen = needle.length slen = s.length pos = start while pos <= slen - nlen if s[pos, nlen] == needle return pos end pos += 1 end -1 end |
.str_hash ⇒ Object
122 123 124 125 126 |
# File 'lib/tep.rb', line 122 def self.str_hash h = {"" => ""} h.delete("") h end |
.timing_safe_eq(a, b) ⇒ Object
Constant-time string equality. Avoids leaking the matching prefix length via early-exit timing. spinel doesn’t have a stdlib crypto-safe compare, so we roll our own.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/tep/session.rb', line 74 def self.timing_safe_eq(a, b) if a.length != b.length return false end diff = 0 i = 0 n = a.length while i < n # getbyte(i), NOT bytes[i]: `String#bytes` allocates a fresh # array on EVERY iteration (O(n^2) garbage). Besides being slow, # that allocation storm drives the GC hard enough to free `b` -- # the HMAC string returned from the Crypto FFI call, held only in # an argument local -- mid-loop, so a valid cookie fails its # signature check ~5% of the time under load (a #1052-family # heap-local rooting gap in spinel, open on master cc94707; the # real fix is upstream, tracked at tep#157). getbyte allocates # nothing, removing the dominant GC trigger here (cuts the flake # ~3x); the residual lives at other unrooted-local sites in the # decode path and clears only when spinel roots heap locals. diff = diff | (a.getbyte(i) ^ b.getbyte(i)) i += 1 end diff == 0 end |
.tls_cert=(v) ⇒ Object
200 |
# File 'lib/tep.rb', line 200 def self.tls_cert=(v); APP.set_tls_cert(v); end |
.tls_key ⇒ Object
201 |
# File 'lib/tep.rb', line 201 def self.tls_key; APP.tls_key; end |
.tls_key=(v) ⇒ Object
202 |
# File 'lib/tep.rb', line 202 def self.tls_key=(v); APP.set_tls_key(v); end |