@rip-lang/server 1.3.98 → 1.3.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,102 @@
1
+ # M0b Review Notes
2
+
3
+ Status: Active
4
+
5
+ ## Accepted decisions
6
+
7
+ - Single project architecture with two runtime roles:
8
+ - EdgeControlPlane
9
+ - AppDataPlane
10
+ - Direct request path remains `client -> ingress -> worker` (no relay hop).
11
+ - v1 scheduler: least-inflight with deterministic tie-break.
12
+ - Config apply lifecycle is atomic (`parse -> validate -> normalize -> stage -> activate/rollback`).
13
+ - Deterministic config evaluation (no async/network I/O).
14
+ - v1 TLS posture reflects M0a findings:
15
+ - HTTP-01 prioritized for reliable v1 ACME
16
+ - TLS-ALPN-01 treated as beta/deferred
17
+ - graceful restart cert activation path
18
+
19
+ ## Deferred / future decisions
20
+
21
+ - Dynamic SNI cert selection when Bun API supports it reliably
22
+ - In-process cert hot reload without restart
23
+ - DNS-01 provider integrations
24
+ - HTTP/2 and gRPC production posture
25
+
26
+ ## Open items for next review pass
27
+
28
+ - Confirm `Edgefile.rip` syntax shape against current Rip parser
29
+ - Validate route precedence edge cases in conformance tests
30
+ - Confirm ops defaults for retention policy in deployment docs
31
+
32
+ ## Refactor Guardrails
33
+
34
+ Group by theme, not by function. A file should cover a coherent area — not
35
+ necessarily one function or one class. Pragmatism over purity.
36
+
37
+ - Group related functions into one file by theme (e.g. all CLI logic in `cli.rip`).
38
+ - No generic `utils.rip` dumping ground — but themed helpers files are fine.
39
+ - If a module is under ~20 lines with only one importer, fold it into its neighbor.
40
+ - If a module grows past ~300 lines, consider splitting by sub-theme.
41
+ - Keep import fan-out low; if a module imports too many siblings, the boundary is wrong.
42
+ - `server.rip` should stay as orchestration/wiring, not business logic.
43
+ - Every extraction or consolidation must pass tests.
44
+ - Prefer vertical domains:
45
+ - `edge/*` for request-path behavior (forwarding, scheduling, status, registry)
46
+ - `control/*` for management (CLI, lifecycle, workers, watchers, mDNS)
47
+ - `acme/*` only when ACME implementation begins
48
+
49
+ ## Coding Conventions
50
+
51
+ ### Bare `try` over `try ... catch then null`
52
+
53
+ In Rip, a bare `try` compiles to `try {} catch {}` in JavaScript — functionally
54
+ identical to `try ... catch then null`, which compiles to `try {} catch { null; }`.
55
+ The extra `null;` is a no-op statement with no behavioral difference.
56
+
57
+ **Preferred style:** use bare `try` for fire-and-forget error suppression.
58
+
59
+ ```coffee
60
+ # Good — clean, concise
61
+ try unlinkSync(path)
62
+ try server.stop()
63
+
64
+ # Good — multiline bare try
65
+ try
66
+ proc.kill()
67
+ console.log "stopped #{host}"
68
+
69
+ # Avoid — unnecessary catch block
70
+ try unlinkSync(path) catch then null
71
+
72
+ # Avoid — verbose no-op catch
73
+ try
74
+ proc.kill()
75
+ catch
76
+ null
77
+ ```
78
+
79
+ **When to use an explicit catch:**
80
+ - When the catch body does actual work (logging, fallback value, cleanup)
81
+ - When catching a specific error variable: `catch e`
82
+ - When fall-through after try needs a specific return value that differs from `undefined`
83
+
84
+ ```coffee
85
+ # Good — catch does real work
86
+ try
87
+ pkg = JSON.parse(readFileSync(path, 'utf8'))
88
+ catch
89
+ console.log 'version unknown'
90
+
91
+ # Good — catch with error variable
92
+ catch e
93
+ console.error "failed: #{e.message}"
94
+ ```
95
+
96
+ ## Evidence links
97
+
98
+ - TLS spikes and outputs:
99
+ - `packages/server/spikes/tls/README.md`
100
+ - `packages/server/spikes/tls/FINDINGS.md`
101
+ - Plan of record:
102
+ - `.cursor/plans/rip_unified_master_plan_v2_2892e891.plan.md`
@@ -0,0 +1,46 @@
1
+ # Scheduler Policy (M0b)
2
+
3
+ This document freezes v1 scheduler behavior.
4
+
5
+ ## Algorithm
6
+
7
+ v1 uses **least-inflight** per app pool.
8
+
9
+ Selection order:
10
+ 1. filter to workers in `ready` state
11
+ 2. choose worker with lowest `inflight`
12
+ 3. tie-break by lowest `workerId`
13
+
14
+ ## Fallback behavior
15
+
16
+ - If all workers are saturated and queue depth `< maxQueue`: enqueue
17
+ - If queue depth `>= maxQueue`: return `503` with `Retry-After`
18
+
19
+ ## Fairness expectations
20
+
21
+ - No worker starvation under steady load
22
+ - No worker starvation under burst load
23
+ - Workload spread should converge over time for equal worker capacity
24
+
25
+ ## Retry policy interaction
26
+
27
+ - Retry only idempotent requests by default
28
+ - Never retry after partial upstream response
29
+ - Retry decisions occur after scheduler selection and only on retry-eligible failures
30
+
31
+ ## Metrics emitted
32
+
33
+ - per-app scheduler decisions
34
+ - per-worker inflight counts
35
+ - queue depth and queue wait duration
36
+ - shed count (`503` due to capacity)
37
+
38
+ ## Change criteria (v2)
39
+
40
+ Consider upgrading algorithm if:
41
+ - tail latency variance between workers > 3x for sustained periods
42
+ - workload variance causes persistent imbalance despite least-inflight
43
+
44
+ Candidate future algorithms:
45
+ - EWMA latency-weighted
46
+ - power-of-two choices
package/middleware.rip CHANGED
@@ -36,7 +36,7 @@
36
36
  import { get, enableCorsPreflight } from '@rip-lang/server'
37
37
  import { fileURLToPath } from 'node:url'
38
38
  import { dirname, resolve } from 'node:path'
39
- import { existsSync } from 'node:fs'
39
+ import { existsSync, realpathSync } from 'node:fs'
40
40
 
41
41
  export cors = (opts = {}) ->
42
42
  origin = opts.origin or '*'
@@ -525,12 +525,14 @@ export serve = (opts = {}) ->
525
525
  serve._registered = true
526
526
 
527
527
  # Route: {prefix}/*.rip — serve .rip files from the app directory tree
528
- appDirResolved = resolve(appDir)
528
+ appDirResolved = realpathSync(resolve(appDir))
529
529
  get "#{prefix}/*.rip" ->
530
530
  name = @req.path.slice("#{prefix}/".length)
531
531
  path = resolve(appDir, name)
532
- return unless path.startsWith(appDirResolved)
533
- @send path, 'text/x-rip; charset=UTF-8' if existsSync(path)
532
+ return unless existsSync(path)
533
+ realPath = realpathSync(path)
534
+ return unless realPath.startsWith(appDirResolved)
535
+ @send realPath, 'text/x-rip; charset=UTF-8'
534
536
 
535
537
  # Routes: {prefix}/{name} — per-bundle JSON endpoints (cached + Brotli)
536
538
  # Built once per worker on first request, then served from memory with ETag/304.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/server",
3
- "version": "1.3.98",
3
+ "version": "1.3.99",
4
4
  "description": "Pure Rip web framework and application server",
5
5
  "type": "module",
6
6
  "main": "api.rip",
@@ -45,7 +45,7 @@
45
45
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
46
46
  "license": "MIT",
47
47
  "dependencies": {
48
- "rip-lang": ">=3.13.108"
48
+ "rip-lang": ">=3.13.109"
49
49
  },
50
50
  "files": [
51
51
  "api.rip",