v0.33.7_docker_hostnames
This commit is contained in:
1 parent
f462ca7194
commit
7e625c5876
6 files changed
+90
-73
No files matched your search
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
## 0.33.7
|
## 0.33.7
|
||||||
|
|
||||||
|
- Docker agent: suggest container names rather than name-port for newly published vhosts. Append port only for conflicts; preserve stable service keys and existing vhost hostnames.
|
||||||
- Replace the redundant **Arrange Overview** visual editor modal with an
|
- Replace the redundant **Arrange Overview** visual editor modal with an
|
||||||
account-scoped Dashboard preset manager. Save, load/overwrite and delete up
|
account-scoped Dashboard preset manager. Save, load/overwrite and delete up
|
||||||
to 20 named presets containing Dashboard density, card order/visibility and
|
to 20 named presets containing Dashboard density, card order/visibility and
|
||||||
|
|||||||
+5
-5
@@ -2,7 +2,7 @@
|
|||||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||||
c793a8be7f4b76350e04ab36dbeec3792bb2dcf33c87a86f76fa2f330e26dd9f AGENTS.md
|
c793a8be7f4b76350e04ab36dbeec3792bb2dcf33c87a86f76fa2f330e26dd9f AGENTS.md
|
||||||
4d86a27c6b8f8931e4c43bbbd0da1a5880ffafd17bd139bd9ba93fa1e7c02af4 CHANGELOG.md
|
538acee273827bd09041e25fe7ee804284eb66ade85537cf3dcd7b968ec33b43 CHANGELOG.md
|
||||||
d5dc2b778af98f52ad52a7515e2d1b4ba8c2e01a04cc6e02564377075b01c501 Cargo.lock
|
d5dc2b778af98f52ad52a7515e2d1b4ba8c2e01a04cc6e02564377075b01c501 Cargo.lock
|
||||||
7fd5dd1ccae8fac77bbbea4bbf960f5cf762f712ff17a2e629a5dd8629eb7ff9 Cargo.toml
|
7fd5dd1ccae8fac77bbbea4bbf960f5cf762f712ff17a2e629a5dd8629eb7ff9 Cargo.toml
|
||||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
||||||
@@ -13,14 +13,14 @@ f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
|||||||
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
||||||
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
|
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
|
||||||
c111f5b20484707b80efb7dd9176acbaa27b63ac78db497261ac234821e1fade dev.sh
|
c111f5b20484707b80efb7dd9176acbaa27b63ac78db497261ac234821e1fade dev.sh
|
||||||
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
|
82fbf6deba6841f843ea5fb419efa03ee820c7fb2af00b1bf4d8f6cddad52b9e docker-agent/README.md
|
||||||
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
|
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
|
||||||
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
|
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
|
||||||
bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/docker.go
|
dcdf8b060d9529ceb8c286d0a592fa4bd876bd7fdcd1a178e4295e13cd88e6c0 docker-agent/docker.go
|
||||||
9e9f3f17f38f9b0730f598f65988ce8ef562d2182ef6778fc7292b990e9cf22f docker-agent/go.mod
|
9e9f3f17f38f9b0730f598f65988ce8ef562d2182ef6778fc7292b990e9cf22f docker-agent/go.mod
|
||||||
9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh
|
9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh
|
||||||
b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go
|
b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go
|
||||||
3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go
|
34552a436bc88a49d1a82a60875d4bd1c0c0b808759b6a099a7f954f44701ab0 docker-agent/main_test.go
|
||||||
68ad007eac216f115809695d3c2c6651e2850ff3d7334685ae6e49bc21b01087 docker-agent/model.go
|
68ad007eac216f115809695d3c2c6651e2850ff3d7334685ae6e49bc21b01087 docker-agent/model.go
|
||||||
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
|
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
|
||||||
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
|
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
|
||||||
@@ -64,7 +64,7 @@ ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.
|
|||||||
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
|
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
|
||||||
84d1523a18dc98d266a290ed7fb8fe2624bb37fd54f4f0b991ba37a7624d8a3a docs/SETTINGS.md
|
84d1523a18dc98d266a290ed7fb8fe2624bb37fd54f4f0b991ba37a7624d8a3a docs/SETTINGS.md
|
||||||
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
|
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
|
||||||
420d8e3976882ef42c2b7dde409560825db72a22405b023876f9c39055671754 docs/TEST_REPORT.md
|
193704656501643c86470be699d2e1c05b5fbb678f68ffac1b3f5c51172bf433 docs/TEST_REPORT.md
|
||||||
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
||||||
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
||||||
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
|
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
|
||||||
|
|||||||
@@ -44,3 +44,7 @@ go build -trimpath -ldflags='-s -w' -o rustproxy-docker-agent .
|
|||||||
The inventory endpoint is `GET /v1/inventory` with `Authorization: Bearer <token>`. `/healthz` does not expose Docker inventory. Both are served over HTTPS by default.
|
The inventory endpoint is `GET /v1/inventory` with `Authorization: Bearer <token>`. `/healthz` does not expose Docker inventory. Both are served over HTTPS by default.
|
||||||
|
|
||||||
`RUSTPROXY_DOCKER_ADVERTISE_IP` marks a preferred address but does not suppress automatic discovery of the host's other addresses. `RUSTPROXY_DOCKER_PUBLIC_IP_URL` adds the externally observed address when enabled. RustProxy can choose a concrete reported address independently for every Docker-backed vhost.
|
`RUSTPROXY_DOCKER_ADVERTISE_IP` marks a preferred address but does not suppress automatic discovery of the host's other addresses. `RUSTPROXY_DOCKER_PUBLIC_IP_URL` adds the externally observed address when enabled. RustProxy can choose a concrete reported address independently for every Docker-backed vhost.
|
||||||
|
|
||||||
|
## Default vhost hostname suggestions
|
||||||
|
|
||||||
|
The agent reports a DNS-safe Docker container name as `technical_name` (or the Compose service name when the container name is unavailable). RustProxy pre-fills this name for newly published vhosts. For example, `/grafana` suggests `grafana`, not `grafana-3000`. If multiple published services have the same normalized container name, the agent appends the container's internal port; remaining collisions get a deterministic identifier. The stable `service_key` is unchanged. Already saved vhost hostnames are never rewritten by discovery.
|
||||||
+43
-6
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -92,20 +93,18 @@ func serviceIdentity(c dockerContainer, p dockerPort) (key, technical string) {
|
|||||||
project := c.Labels["com.docker.compose.project"]
|
project := c.Labels["com.docker.compose.project"]
|
||||||
composeService := c.Labels["com.docker.compose.service"]
|
composeService := c.Labels["com.docker.compose.service"]
|
||||||
replica := c.Labels["com.docker.compose.container-number"]
|
replica := c.Labels["com.docker.compose.container-number"]
|
||||||
suffix := strconv.Itoa(int(p.PrivatePort))
|
|
||||||
if project != "" && composeService != "" {
|
if project != "" && composeService != "" {
|
||||||
if replica == "" {
|
if replica == "" {
|
||||||
replica = name
|
replica = name
|
||||||
}
|
}
|
||||||
key = fmt.Sprintf("compose:%s/%s/%s:%d/%s", project, composeService, replica, p.PrivatePort, p.Type)
|
key = fmt.Sprintf("compose:%s/%s/%s:%d/%s", project, composeService, replica, p.PrivatePort, p.Type)
|
||||||
technicalBase := project + "-" + composeService
|
technical = dnsLabel(name)
|
||||||
if replica != "1" {
|
if name == "container" {
|
||||||
technicalBase += "-" + replica
|
technical = dnsLabel(composeService)
|
||||||
}
|
}
|
||||||
technical = dnsLabel(technicalBase + "-" + suffix)
|
|
||||||
} else {
|
} else {
|
||||||
key = fmt.Sprintf("container:%s:%d/%s", name, p.PrivatePort, p.Type)
|
key = fmt.Sprintf("container:%s:%d/%s", name, p.PrivatePort, p.Type)
|
||||||
technical = dnsLabel(name + "-" + suffix)
|
technical = dnsLabel(name)
|
||||||
}
|
}
|
||||||
return key, technical
|
return key, technical
|
||||||
}
|
}
|
||||||
@@ -163,5 +162,43 @@ func servicesFrom(containers []dockerContainer) []service {
|
|||||||
out = append(out, *item)
|
out = append(out, *item)
|
||||||
}
|
}
|
||||||
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
||||||
|
// Prefer the Docker container name. Add the internal port only when names
|
||||||
|
// collide; retain stable service keys and prevent any duplicate hostnames.
|
||||||
|
counts := make(map[string]int, len(out))
|
||||||
|
for _, item := range out {
|
||||||
|
counts[item.TechnicalName]++
|
||||||
|
}
|
||||||
|
for i := range out {
|
||||||
|
if counts[out[i].TechnicalName] > 1 {
|
||||||
|
out[i].TechnicalName = dnsLabel(out[i].TechnicalName + "-" + strconv.Itoa(int(out[i].PrivatePort)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Port suffixes can still collide after normalization/truncation.
|
||||||
|
// Resolve these deterministically using a short hash of the stable key.
|
||||||
|
counts = make(map[string]int, len(out))
|
||||||
|
for _, item := range out {
|
||||||
|
counts[item.TechnicalName]++
|
||||||
|
}
|
||||||
|
used := make(map[string]struct{}, len(out))
|
||||||
|
for i := range out {
|
||||||
|
name := out[i].TechnicalName
|
||||||
|
if counts[name] > 1 {
|
||||||
|
digest := sha256.Sum256([]byte(out[i].Key))
|
||||||
|
name = fmt.Sprintf("%s-%x", strings.TrimRight(name[:min(len(name), 48)], "-"), digest[:6])
|
||||||
|
}
|
||||||
|
// Extremely unlikely hash collisions are still resolved without
|
||||||
|
// changing the stable key or depending on Docker listing order.
|
||||||
|
for attempt := 0; ; attempt++ {
|
||||||
|
candidate := name
|
||||||
|
if attempt > 0 {
|
||||||
|
candidate = fmt.Sprintf("%s-%d", name[:min(len(name), 56)], attempt)
|
||||||
|
}
|
||||||
|
if _, exists := used[candidate]; !exists {
|
||||||
|
out[i].TechnicalName = candidate
|
||||||
|
used[candidate] = struct{}{}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ func TestServicesFromUsesStableComposeIdentity(t *testing.T) {
|
|||||||
if len(got) != 1 {
|
if len(got) != 1 {
|
||||||
t.Fatalf("got %d services", len(got))
|
t.Fatalf("got %d services", len(got))
|
||||||
}
|
}
|
||||||
if got[0].Key != "compose:lab/web/1:8080/tcp" || got[0].TechnicalName != "lab-web-8080" {
|
if got[0].Key != "compose:lab/web/1:8080/tcp" || got[0].TechnicalName != "web-1" {
|
||||||
t.Fatalf("unexpected service %#v", got[0])
|
t.Fatalf("unexpected service %#v", got[0])
|
||||||
}
|
}
|
||||||
if len(got[0].Bindings) != 1 || got[0].Bindings[0].PublicPort != 18080 {
|
if len(got[0].Bindings) != 1 || got[0].Bindings[0].PublicPort != 18080 {
|
||||||
@@ -72,6 +72,42 @@ func TestServicesFromKeepsComposeReplicasAndAllBindings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServiceNamesPreferContainerAndOnlyAppendPortsForCollisions(t *testing.T) {
|
||||||
|
items := []dockerContainer{
|
||||||
|
{ID: "a", Names: []string{"/grafana"}, Labels: map[string]string{}, Ports: []dockerPort{{PrivatePort: 3000, PublicPort: 3000, Type: "tcp", IP: "0.0.0.0"}}},
|
||||||
|
{ID: "b", Names: []string{"/frontend"}, Labels: map[string]string{"com.docker.compose.project": "example", "com.docker.compose.service": "web", "com.docker.compose.container-number": "1"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 8080, Type: "tcp", IP: "0.0.0.0"}}},
|
||||||
|
{ID: "c", Names: []string{"/shared"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 8081, Type: "tcp", IP: "0.0.0.0"}, {PrivatePort: 443, PublicPort: 8443, Type: "tcp", IP: "0.0.0.0"}}},
|
||||||
|
}
|
||||||
|
got := servicesFrom(items)
|
||||||
|
names := map[string]string{}
|
||||||
|
for _, item := range got {
|
||||||
|
names[item.Key] = item.TechnicalName
|
||||||
|
}
|
||||||
|
if names["container:grafana:3000/tcp"] != "grafana" || names["compose:example/web/1:80/tcp"] != "frontend" {
|
||||||
|
t.Fatalf("incorrect short names: %#v", names)
|
||||||
|
}
|
||||||
|
if names["container:shared:80/tcp"] != "shared-80" || names["container:shared:443/tcp"] != "shared-443" {
|
||||||
|
t.Fatalf("incorrect collision names: %#v", names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceNamesRemainUniqueAfterDNSNormalization(t *testing.T) {
|
||||||
|
items := []dockerContainer{
|
||||||
|
{Names: []string{"/A_B"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 18080, Type: "tcp"}}},
|
||||||
|
{Names: []string{"/a-b"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 18081, Type: "tcp"}}},
|
||||||
|
}
|
||||||
|
got := servicesFrom(items)
|
||||||
|
if len(got) != 2 || got[0].TechnicalName == got[1].TechnicalName {
|
||||||
|
t.Fatalf("names not unique: %#v", got)
|
||||||
|
}
|
||||||
|
reversed := servicesFrom([]dockerContainer{items[1], items[0]})
|
||||||
|
for i := range got {
|
||||||
|
if got[i].TechnicalName != reversed[i].TechnicalName {
|
||||||
|
t.Fatalf("unstable names: %#v vs %#v", got, reversed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServicesFromSkipsLoopbackOnlyBindings(t *testing.T) {
|
func TestServicesFromSkipsLoopbackOnlyBindings(t *testing.T) {
|
||||||
items := []dockerContainer{{ID: "abc", Names: []string{"/app"}, Labels: map[string]string{}, Ports: []dockerPort{
|
items := []dockerContainer{{ID: "abc", Names: []string{"/app"}, Labels: map[string]string{}, Ports: []dockerPort{
|
||||||
{IP: "127.0.0.1", PrivatePort: 8080, PublicPort: 18080, Type: "tcp"},
|
{IP: "127.0.0.1", PrivatePort: 8080, PublicPort: 18080, Type: "tcp"},
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
# RustProxy 0.33.7 - verification report
|
|
||||||
|
|
||||||
## Scope (2026-10-15)
|
|
||||||
|
|
||||||
0.33.7 changes the Dashboard UI without changing proxy traffic handling or
|
|
||||||
telemetry collection:
|
|
||||||
|
|
||||||
- Replace the duplicated **Arrange Overview** editor modal with an account-scoped
|
|
||||||
**Dashboard presets** manager. A named preset stores Dashboard density, card
|
|
||||||
order, visibility and optional custom widths; presets can be saved/updated,
|
|
||||||
loaded and deleted through the existing administrator preferences document.
|
|
||||||
- Keep actual Dashboard editing in the existing inline **Unlock layout** flow and
|
|
||||||
Settings visibility/order controls.
|
|
||||||
- Use the same monospace font family as **CONNECTION ACTIVITY** for Overview chart
|
|
||||||
titles, including compact chart cards and wide charts.
|
|
||||||
- No SQLite schema migration, new telemetry polling loop or proxy-path change is
|
|
||||||
introduced.
|
|
||||||
|
|
||||||
## Checks completed
|
|
||||||
|
|
||||||
- **PASS:** `python scripts/tests/dashboard_presets_0337_browser_test.py` against
|
|
||||||
the real bundled HTML/CSS/JS with a simulated preferences API at 320, 375,
|
|
||||||
768 and 1440px. Covered save/update snapshot data, load, delete, responsive
|
|
||||||
modal layout, removal of the former duplicated editor controls, and chart-title
|
|
||||||
font parity with **CONNECTION ACTIVITY**.
|
|
||||||
- **PASS:** `python scripts/tests/dashboard_modal_0333_browser_test.py` after the
|
|
||||||
0.33.7 preset conversion at 320, 375, 550, 620, 800, 900, 960, 1158, 1440 and
|
|
||||||
1920px with no horizontal viewport overflow.
|
|
||||||
- **PASS:** updated `dashboard_layout_0331_browser_test.py` and
|
|
||||||
`dashboard_resizing_0333_browser_test.py`; inline editing still covers
|
|
||||||
add/hide/reorder/resize, live refresh, cancel/save and persisted custom widths,
|
|
||||||
while opening/closing the preset modal does not mutate the active layout.
|
|
||||||
- **PASS:** all 13 current `scripts/tests/*browser_test.py` simulated-browser
|
|
||||||
regressions, including chart heading/width/fixed CPU/stable scale coverage,
|
|
||||||
progress bars, sidebar, RAM, Web observability and Web Server live metrics.
|
|
||||||
The obsolete CPU autoscale regression was removed because 0.33.5 intentionally
|
|
||||||
replaced that behavior with a fixed 0-100% CPU scale and has its own current
|
|
||||||
regression test.
|
|
||||||
- **PASS:** `node scripts/tests/overview_samples_0330_ui_test.mjs`,
|
|
||||||
`node scripts/tests/dashboard_resume_rate_charts_ui_test.mjs` and
|
|
||||||
`node scripts/tests/dashboard_metric_visuals_ui_test.mjs`.
|
|
||||||
- **PASS:** JSON parsing for `src/admin/openapi.json`, `langs/en_US.json` and
|
|
||||||
`langs/pl_pl.json`; JavaScript syntax checks for the changed preferences/preset
|
|
||||||
files; Python syntax checks for changed browser tests; `python scripts/preflight.py`.
|
|
||||||
- **PASS:** `./dev.sh doctor` reports the source layout as OK and confirms that a
|
|
||||||
C compiler and Python are available.
|
|
||||||
- **PASS:** all eight `migrations/*.sql` applied successfully in two successive
|
|
||||||
cycles to a disposable in-memory SQLite database. This verifies SQL script
|
|
||||||
execution/idempotency in this context, not a production-data upgrade.
|
|
||||||
- **PASS:** parsed `scripts/deploy/upgrade.py --help` and
|
|
||||||
`scripts/migration/migrate_npm.py --help`; no live external upgrade/migration
|
|
||||||
was performed.
|
|
||||||
|
|
||||||
## Not verified in this environment
|
|
||||||
|
|
||||||
- `./dev.sh check` and `./dev.sh test` exit 127 because Cargo/rustc are not
|
|
||||||
installed. Therefore Rust compilation, Rust unit/integration tests, an actual
|
|
||||||
RustProxy process, real SSE traffic, production database upgrade and live load
|
|
||||||
testing are not verified here.
|
|
||||||
- The real-runtime acceptance gate remains open until Rust builds and runs against
|
|
||||||
representative test traffic.
|
|
||||||
Reference in new issue
Block a user