From b9df44575400ed1d1e3cda52dfd7957a372aa71f Mon Sep 17 00:00:00 2001 From: Tyler <53561637+im-tyler@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:16:10 -0700 Subject: [PATCH 1/4] fix(retention): expire LLM traces with a configured window --- AUDIT_OPEN.md | 11 +++++++ README.md | 1 + cmd/observe/main.go | 2 +- internal/config/config.go | 5 +++ internal/config/config_test.go | 18 +++++++++-- internal/jobs/retention.go | 10 +++--- internal/jobs/retention_test.go | 54 +++++++++++++++++++++++++++++++++ 7 files changed, 93 insertions(+), 8 deletions(-) diff --git a/AUDIT_OPEN.md b/AUDIT_OPEN.md index f67f77f..86db5eb 100644 --- a/AUDIT_OPEN.md +++ b/AUDIT_OPEN.md @@ -14,6 +14,17 @@ report lives outside the repo) — remediation record below. Round 4: audit passes 1-5) are closed history; their one surviving item is folded into F16 below. +## L10 (2026-09-27) — Fixed in source: LLM traces omitted from retention + +LLM traces, including prompt/completion payloads, were never expired by the +scheduled cleanup. Added a validated OBSERVE_LLM_RETENTION_DAYS setting (30-day +default) and included the table in the existing bounded cleanup path. Historical +model-price catalog entries remain independent of trace retention. Regression +coverage checks expiry and preservation against a real Nucleus fixture. +This fixes unbounded logical trace retention; it does not claim SQL DELETE +shrinks files or establish this table as the cause of a past host incident. +Deployment and sustained storage measurements remain separate acceptance gates. + ## L9 (2026-09-24) - P1 - Open: live llm_traces unreadable after migration 054; ALTER-ADD migrations Found deploying `d4bcbe6` (the zombie-reaping image fix) to infra-home, the diff --git a/README.md b/README.md index 60e4da4..08fdd37 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,7 @@ observeErrors.addBreadcrumb({ type: "user", category: "click", message: "Button" | `OBSERVE_REQUIRE_WAL` | (unset) | Set to `true` (or `1`) to refuse to start when WAL-backed ingestion durability is unavailable, instead of degrading to memory-only. | | `OBSERVE_RAW_RETENTION_DAYS` | `30` | Raw event retention. Also the window over which visitor counts are exact from raw events; past it they are counted from the `sessions` table (90 days), and past both the dashboard says which window the figure covers. | | `OBSERVE_HOURLY_RETENTION_DAYS` | `365` | Hourly rollup retention. | +| `OBSERVE_LLM_RETENTION_DAYS` | `30` | LLM trace retention, including stored prompts/completions. Must be at least 1. Cleanup runs daily; historical model-price catalog entries are retained. Logical expiry does not guarantee database files immediately shrink. | | `OBSERVE_LOG_ROUTES` | `0` | Set to `1` to print route table at boot. | | `OBSERVE_SMTP_HOST` | | SMTP server for email reports. | | `OBSERVE_SMTP_PORT` | `587` | SMTP port. | diff --git a/cmd/observe/main.go b/cmd/observe/main.go index 74cda57..ab90b0e 100644 --- a/cmd/observe/main.go +++ b/cmd/observe/main.go @@ -372,7 +372,7 @@ func main() { // same numbers rather than a constant of its own. The ledger policies // (O01 slice 5) ride the same job: dedupe ledgers expire with the data // they dedupe, processed outbox intents prune, dead letters never do. - retentionPolicies := append(jobs.DefaultPolicies(cfg.RawRetentionDays, cfg.HourlyRetentionDays), + retentionPolicies := append(jobs.DefaultPolicies(cfg.RawRetentionDays, cfg.HourlyRetentionDays, cfg.LLMRetentionDays), jobs.DefaultLedgerPolicies(cfg.ErrorInboxRetentionDays, cfg.ReplayBatchesRetentionDays, cfg.DerivedOutboxRetentionDays)...) // Stats service diff --git a/internal/config/config.go b/internal/config/config.go index 35fc60a..af11cbe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -33,6 +33,7 @@ type Config struct { // Retention RawRetentionDays int HourlyRetentionDays int + LLMRetentionDays int // Ledger retention (O01 ADR 5.8, decided 2026-09-23). Expiry means a // retried record is processed as new — no silent infinite exactly-once // promise beyond the window. @@ -95,6 +96,7 @@ func Load() Config { set("OBSERVE_FLUSH_SIZE", 500, &c.FlushSize) set("OBSERVE_RAW_RETENTION_DAYS", 30, &c.RawRetentionDays) set("OBSERVE_HOURLY_RETENTION_DAYS", 365, &c.HourlyRetentionDays) + set("OBSERVE_LLM_RETENTION_DAYS", 30, &c.LLMRetentionDays) // O01 slice 5 ledger retention (decided defaults 2026-09-23): the // error_inbox and replay_batches ledgers must not outlive the data they // dedupe (error_events 180d, replay_sessions 14d); derived_outbox @@ -168,6 +170,9 @@ func (c Config) Validate() error { if c.HourlyRetentionDays < 1 { return fmt.Errorf("OBSERVE_HOURLY_RETENTION_DAYS must be >= 1, got %d", c.HourlyRetentionDays) } + if c.LLMRetentionDays < 1 { + return fmt.Errorf("OBSERVE_LLM_RETENTION_DAYS must be >= 1, got %d", c.LLMRetentionDays) + } if c.ErrorInboxRetentionDays < 1 { return fmt.Errorf("OBSERVE_ERROR_INBOX_RETENTION_DAYS must be >= 1, got %d", c.ErrorInboxRetentionDays) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 0167f80..51b23a6 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -24,6 +24,7 @@ func TestValidateRejectsBrokenNumericConfig(t *testing.T) { {"negative interval", func(c *Config) { c.FlushInterval = -time.Second }, "OBSERVE_FLUSH_INTERVAL_MS"}, {"huge interval", func(c *Config) { c.FlushInterval = 10 * time.Minute }, "OBSERVE_FLUSH_INTERVAL_MS"}, {"zero rate limit", func(c *Config) { c.RateLimit = 0 }, "OBSERVE_RATE_LIMIT"}, + {"zero LLM retention", func(c *Config) { c.LLMRetentionDays = 0 }, "OBSERVE_LLM_RETENTION_DAYS"}, {"zero retention", func(c *Config) { c.RawRetentionDays = 0 }, "OBSERVE_RAW_RETENTION_DAYS"}, {"malformed integer", func(c *Config) { c.parseErr = fmt.Errorf("OBSERVE_BUFFER_SIZE must be an integer, got %q", "big") @@ -32,7 +33,7 @@ func TestValidateRejectsBrokenNumericConfig(t *testing.T) { for _, tc := range cases { c := Config{ BufferSize: 1000, FlushSize: 100, FlushInterval: time.Second, - RateLimit: 100, RawRetentionDays: 30, HourlyRetentionDays: 365, + RateLimit: 100, RawRetentionDays: 30, HourlyRetentionDays: 365, LLMRetentionDays: 30, ErrorInboxRetentionDays: 14, ReplayBatchesRetentionDays: 14, DerivedOutboxRetentionDays: 7, } tc.mut(&c) @@ -46,10 +47,23 @@ func TestValidateRejectsBrokenNumericConfig(t *testing.T) { func TestValidateAcceptsDefaults(t *testing.T) { c := Config{ BufferSize: 100_000, FlushSize: 500, FlushInterval: 2 * time.Second, - RateLimit: 1000, RawRetentionDays: 30, HourlyRetentionDays: 365, + RateLimit: 1000, RawRetentionDays: 30, HourlyRetentionDays: 365, LLMRetentionDays: 30, ErrorInboxRetentionDays: 14, ReplayBatchesRetentionDays: 14, DerivedOutboxRetentionDays: 7, } if err := c.Validate(); err != nil { t.Fatalf("default-shaped config must validate: %v", err) } } + +func TestLoadLLMRetentionWindow(t *testing.T) { + t.Setenv("OBSERVE_LLM_RETENTION_DAYS", "7") + c := Load() + if c.LLMRetentionDays != 7 { + t.Fatalf("LLM retention=%d, want 7", c.LLMRetentionDays) + } + t.Setenv("OBSERVE_LLM_RETENTION_DAYS", "invalid") + c = Load() + if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "OBSERVE_LLM_RETENTION_DAYS") { + t.Fatalf("invalid LLM duration must fail validation, got %v", err) + } +} diff --git a/internal/jobs/retention.go b/internal/jobs/retention.go index a00e10b..0c040e0 100644 --- a/internal/jobs/retention.go +++ b/internal/jobs/retention.go @@ -47,10 +47,9 @@ type RetentionService struct { policies []RetentionPolicy } -// DefaultPolicies returns the out-of-box retention policies. Called with the two -// legacy env-configured durations for raw events + hourly rollups so existing -// deployments keep their behavior. -func DefaultPolicies(rawDays, hourlyDays int) []RetentionPolicy { +// DefaultPolicies returns the out-of-box retention policies, using configured +// durations for raw events, hourly rollups and LLM traces. +func DefaultPolicies(rawDays, hourlyDays, llmDays int) []RetentionPolicy { return []RetentionPolicy{ {Table: "events", Column: "timestamp", Days: rawDays}, {Table: "events_recent", Column: "timestamp", Days: 7}, @@ -58,6 +57,7 @@ func DefaultPolicies(rawDays, hourlyDays int) []RetentionPolicy { {Table: "sessions", Column: "last_ts", Days: 90}, {Table: "error_events", Column: "timestamp", Days: 180}, {Table: "logs", Column: "timestamp", Days: 30}, + {Table: "llm_traces", Column: "timestamp", Days: llmDays}, {Table: "spans", Column: "start_time", Days: 14}, {Table: "service_stats", Column: "ts_bucket", Days: 30}, {Table: "replay_sessions", Column: "start_time", Days: 14}, @@ -111,7 +111,7 @@ func PolicyDays(policies []RetentionPolicy, table string) int { // NewRetentionService keeps the old two-arg constructor for backwards compat. func NewRetentionService(db *nucleus.Client, logger *slog.Logger, rawDays, hourlyDays int) *RetentionService { - return NewRetentionServiceWithPolicies(db, logger, DefaultPolicies(rawDays, hourlyDays)) + return NewRetentionServiceWithPolicies(db, logger, DefaultPolicies(rawDays, hourlyDays, 30)) } // NewRetentionServiceWithPolicies allows callers to supply a fully custom policy set. diff --git a/internal/jobs/retention_test.go b/internal/jobs/retention_test.go index 6f3465c..e03a8ca 100644 --- a/internal/jobs/retention_test.go +++ b/internal/jobs/retention_test.go @@ -155,3 +155,57 @@ func TestRetentionPrunesProcessedOutboxIntentsNotDeadLetters(t *testing.T) { t.Errorf("dead-lettered intent was auto-pruned (got %d, want 1) — dead letters are the operator's queue", c) } } + +// Trace expiry must use the configured window without erasing the price catalog +// needed to explain historical cost estimates. +func TestLLMRetentionExpiresOldTracesPreservesRecentAndCatalog(t *testing.T) { + ctx, db, done := connect(t) + defer done() + site := "llm_ret_" + strconv.FormatInt(time.Now().UnixNano(), 36) + now := time.Now().UnixMilli() + const day = int64(24 * 60 * 60 * 1000) + for _, row := range []struct { + id string + ts int64 + }{{"old", now - 8*day}, {"recent", now - 6*day}} { + if _, err := db.SQL().Exec(ctx, `INSERT INTO llm_traces (trace_id, site_id, timestamp, prompt, completion) VALUES ($1,$2,$3,$4,$5)`, site+row.id, site, row.ts, "retained input", "retained output"); err != nil { + t.Fatal(err) + } + } + if _, err := db.SQL().Exec(ctx, `INSERT INTO llm_model_prices (model_prefix, created_at, valid_from) VALUES ($1,$2,$3)`, site, now-40*day, now-40*day); err != nil { + t.Fatal(err) + } + policies := DefaultPolicies(30, 365, 7) + var selected []RetentionPolicy + for _, p := range policies { + if p.Table == "llm_traces" { + selected = append(selected, p) + } + } + if len(selected) != 1 { + t.Fatalf("expected exactly one LLM policy, got %d", len(selected)) + } + svc := NewRetentionServiceWithPolicies(db, slog.New(slog.NewTextHandler(io.Discard, nil)), selected) + if err := svc.RunCleanup(ctx); err != nil { + t.Fatal(err) + } + for _, row := range []struct { + id string + want int64 + }{{"old", 0}, {"recent", 1}} { + got, err := nucleus.Query[countRow](ctx, db.SQL(), `SELECT COUNT(*) AS n FROM llm_traces WHERE trace_id=$1`, site+row.id) + if err != nil { + t.Fatal(err) + } + if len(got) != 1 || got[0].N != row.want { + t.Errorf("%s trace count=%v, want %d", row.id, got, row.want) + } + } + got, err := nucleus.Query[countRow](ctx, db.SQL(), `SELECT COUNT(*) AS n FROM llm_model_prices WHERE model_prefix=$1`, site) + if err != nil { + t.Fatal(err) + } + if len(got) != 1 || got[0].N != 1 { + t.Fatalf("price catalog was removed: %v", got) + } +} From a28cea6856b7077b2ca845baff47a91e580c79e2 Mon Sep 17 00:00:00 2001 From: Tyler <53561637+im-tyler@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:23:20 -0700 Subject: [PATCH 2/4] fix(ci): honor UI shell and workspace paths and valid test secrets --- .github/workflows/ci.yml | 7 ++++--- scripts/ui-sync.sh | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61c0b1c..a308871 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,9 +154,10 @@ jobs: - name: Provision TS workspace run: pnpm -C ../Neutron/typescript install --frozen-lockfile - name: ui-sync (build + embed) - run: sh scripts/ui-sync.sh + run: bash scripts/ui-sync.sh env: OBSERVE_BIN: /tmp/observe-ui-sync + NEUTRON_ROOT: ${{ github.workspace }}/../Neutron - name: Embedded dist must be fresh run: git diff --exit-code -- cmd/observe/ui/dist @@ -244,7 +245,7 @@ jobs: run: | OBSERVE_NUCLEUS_URL='postgres://nucleus@127.0.0.1:55432/observe?sslmode=disable' \ OBSERVE_ADMIN_PASSWORD=ci-migration-boot \ - OBSERVE_JWT_SECRET=ci-migration-secret \ + OBSERVE_JWT_SECRET=ci-only-migration-secret-at-least-32-characters \ /tmp/observe & OBS_PID=$! for i in $(seq 1 90); do @@ -292,7 +293,7 @@ jobs: run: | OBSERVE_NUCLEUS_URL='postgres://nucleus@127.0.0.1:55432/observe?sslmode=disable' \ OBSERVE_ADMIN_PASSWORD=ci-migration-boot \ - OBSERVE_JWT_SECRET=ci-migration-secret \ + OBSERVE_JWT_SECRET=ci-only-migration-secret-at-least-32-characters \ /tmp/observe & OBS_PID=$! for i in $(seq 1 90); do diff --git a/scripts/ui-sync.sh b/scripts/ui-sync.sh index dc0f814..160318f 100755 --- a/scripts/ui-sync.sh +++ b/scripts/ui-sync.sh @@ -8,7 +8,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OBSERVE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" OBSERVE_UI_SRC="$OBSERVE_ROOT/ui/src" -CANONICAL_APP="$OBSERVE_ROOT/../../Neutron/typescript/apps/observe" +NEUTRON_ROOT="${NEUTRON_ROOT:-$OBSERVE_ROOT/../../Neutron}" +CANONICAL_APP="$NEUTRON_ROOT/typescript/apps/observe" CANONICAL_SRC="$CANONICAL_APP/src" EMBED_DIST="$OBSERVE_ROOT/cmd/observe/ui/dist" BIN_OUT="${OBSERVE_BIN:-/tmp/obs-launch/observe}" From e5d1342bf6fa61cccc9410114437b2f3b026e206 Mon Sep 17 00:00:00 2001 From: Tyler <53561637+im-tyler@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:33:48 -0700 Subject: [PATCH 3/4] Make UI rebuilds reproducible and test supported Nucleus on amd64 --- .ci/observe-workspace.patch | 57 ++++++++++++++++++ .ci/prepare-ui-workspace.mjs | 22 +++++++ .github/workflows/ci.yml | 13 ++-- AUDIT_OPEN.md | 21 +++++++ .../ui/dist/.neutron-adapter-static.json | 7 +-- .../ui/dist/.neutron-static-policy.json | 1 - .../ui/dist/.neutron-static-policy.json.br | 3 +- .../ui/dist/.neutron-static-policy.json.gz | Bin 246 -> 211 bytes scripts/normalize-ui-metadata.mjs | 28 +++++++++ scripts/ui-sync.sh | 2 + 10 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 .ci/observe-workspace.patch create mode 100644 .ci/prepare-ui-workspace.mjs create mode 100644 scripts/normalize-ui-metadata.mjs diff --git a/.ci/observe-workspace.patch b/.ci/observe-workspace.patch new file mode 100644 index 0000000..4634728 --- /dev/null +++ b/.ci/observe-workspace.patch @@ -0,0 +1,57 @@ +--- a/pnpm-lock.yaml ++++ b/pnpm-lock.yaml +@@ -58,6 +58,25 @@ + vite: + specifier: ^6.0.7 + version: 6.4.1(@types/node@25.9.1)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) ++ ++ apps/observe: ++ dependencies: ++ '@neutron-build/cli': ++ specifier: workspace:* ++ version: link:../../packages/neutron-cli ++ '@neutron-build/core': ++ specifier: workspace:* ++ version: link:../../packages/neutron ++ preact: ++ specifier: 10.25.4 ++ version: 10.25.4 ++ uplot: ++ specifier: 1.6.31 ++ version: 1.6.31 ++ devDependencies: ++ typescript: ++ specifier: 5.7.2 ++ version: 5.7.2 + + apps/playground: + dependencies: +@@ -4860,6 +4879,9 @@ + peerDependencies: + browserslist: '>= 4.21.0' + ++ uplot@1.6.31: ++ resolution: {integrity: sha512-sQZqSwVCbJGnFB4IQjQYopzj5CoTZJ4Br1fG/xdONimqgHmsacvCjNesdGDypNKFbrhLGIeshYhy89FxPF+H+w==} ++ + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + version: 1.3.3 +@@ -9722,6 +9744,8 @@ + escalade: 3.2.0 + picocolors: 1.1.1 + ++ uplot@1.6.31: {} ++ + use-callback-ref@1.3.3(@types/react@19.2.14)(react-compat-shim@file:compat-matrix/shims/react): + dependencies: + react: react-compat-shim@file:compat-matrix/shims/react +--- a/pnpm-workspace.yaml ++++ b/pnpm-workspace.yaml +@@ -6,7 +6,6 @@ + # workspace graph became machine-dependent: a snapshot written locally listed + # observe-dashboard and CI could never reproduce it. The workspace definition + # has to describe the repository, not whatever happens to be on disk. +- - "!apps/observe" + - "examples/*" + # Ecosystem compatibility harness (A-007). Never published. + - "compat-matrix" diff --git a/.ci/prepare-ui-workspace.mjs b/.ci/prepare-ui-workspace.mjs new file mode 100644 index 0000000..515410e --- /dev/null +++ b/.ci/prepare-ui-workspace.mjs @@ -0,0 +1,22 @@ +// Assemble the external Observe app in a disposable, pinned Neutron checkout. +// The patch adds only this importer and its missing dependency to the lockfile. +import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { execFileSync } from 'node:child_process'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +if (!process.argv[2]) throw new Error('usage: node .ci/prepare-ui-workspace.mjs '); +const workspace = resolve(process.argv[2], 'typescript'); +const app = resolve(workspace, 'apps/observe'); +if (existsSync(app)) throw new Error(`Refusing to overwrite an existing app: ${app}`); +execFileSync('patch', ['--dry-run', '-p1', '-i', resolve(root, '.ci/observe-workspace.patch')], { cwd: workspace, stdio: 'inherit' }); +execFileSync('patch', ['-p1', '-i', resolve(root, '.ci/observe-workspace.patch')], { cwd: workspace, stdio: 'inherit' }); +mkdirSync(app, { recursive: true }); +const pkg = JSON.parse(readFileSync(resolve(root, 'ui/package.json'), 'utf8')); +for (const [oldName, newName] of [['neutron', '@neutron-build/core'], ['neutron-cli', '@neutron-build/cli']]) { + pkg.dependencies[newName] = pkg.dependencies[oldName]; + delete pkg.dependencies[oldName]; +} +writeFileSync(resolve(app, 'package.json'), JSON.stringify(pkg, null, 2) + '\n'); +writeFileSync(resolve(app, 'neutron.config.ts'), readFileSync(resolve(root, 'ui/neutron.config.ts'), 'utf8').replace('"neutron"', '"@neutron-build/core"')); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a308871..0e6c953 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,8 +151,12 @@ jobs: corepack enable corepack prepare "pnpm@$version" --activate pnpm --version + - name: Add Observe to the isolated pinned workspace + run: node .ci/prepare-ui-workspace.mjs ../Neutron - name: Provision TS workspace run: pnpm -C ../Neutron/typescript install --frozen-lockfile + - name: Build framework and CLI from the pinned source + run: pnpm -C ../Neutron/typescript --filter '@neutron-build/cli...' run build - name: ui-sync (build + embed) run: bash scripts/ui-sync.sh env: @@ -171,10 +175,9 @@ jobs: # 027) was fixed upstream on 2026-09-18 (Neutron 6286531a) and # repo-built engines pass the full migration ladder again — the # lease-capability job below builds from the pin and relies on it. This - # job keeps the published v0.1.8 fixture for compatibility coverage; - # its known flake (intermittent committed-upsert loss on accumulated - # data, also fixed in the tree) is why the capability job exists - # separately rather than replacing this one. + # Supported-release coverage now uses v1.1.1 on both architectures. + # v0.1.8 no longer satisfies the full suite (leases, parameterized paging, + # and committed upserts); its failing run is recorded in AUDIT_OPEN.md. # X01: the vendor/ tree is what actually compiles, but go.mod's replace # points at a LOCAL Neutron checkout — nothing proved vendor/ matches the # pinned submodule until this check. Drift here means the shipped build @@ -278,7 +281,7 @@ jobs: -e NUCLEUS_ALLOW_NO_AUTH=1 \ -e NUCLEUS_ALLOW_INSECURE_CLUSTER=1 \ -e NUCLEUS_ALLOW_INSECURE_REPLICATION=1 \ - ghcr.io/neutron-build/nucleus:v0.1.8 \ + ghcr.io/neutron-build/nucleus:v1.1.1 \ start --host 0.0.0.0 --port 5432 --cluster-port 5433 --data /data --max-memory 512 for i in $(seq 1 60); do if (exec 3<>/dev/tcp/127.0.0.1/55432) 2>/dev/null; then exec 3>&-; exit 0; fi diff --git a/AUDIT_OPEN.md b/AUDIT_OPEN.md index 86db5eb..78fc56f 100644 --- a/AUDIT_OPEN.md +++ b/AUDIT_OPEN.md @@ -2372,3 +2372,24 @@ Open items this slice deliberately leaves (programme O11 tails): - Package publication prep is owner-controlled per the O11 spec: npm tarball dry-runs (browser, sentry-shim), PyPI sdist/wheel build, Go module tagging — none attempted from the lane. + +## 2026-09-27 — reproducible UI and supported-engine CI + +CI run 36336753676 proved the complete suite on Nucleus v1.1.1 arm64 +and the source-pinned snapshot-lease suite. Its v0.1.8 amd64 job failed +lease expectations, parameterized paging, and committed-upsert assertions. +That obsolete release is not a passing compatibility target. The amd64 +gate now uses v1.1.1, matching the existing arm64 gate; no tests are skipped +or weakened. The failed historical run remains the compatibility receipt. + +The pinned Neutron repository deliberately excludes the external Observe app. +CI now assembles it in its isolated checkout using a checked-in lockfile patch, +then installs frozen dependencies and builds the framework/CLI before ui-sync. +No developer-local canonical app or uncommitted shared Neutron changes are used. +Embedded-asset freshness remains a required check. + +A clean rebuild matched every application asset but exposed upstream static +adapter wall-clock timestamps. ui-sync normalizes those metadata timestamps +and their compressed policy/size counts before embedding; all application +assets remain byte-checked. Upstream report: private UPSTREAM_BUGS.md entry +2026-09-27. Two local builds and normalization idempotence verified. diff --git a/cmd/observe/ui/dist/.neutron-adapter-static.json b/cmd/observe/ui/dist/.neutron-adapter-static.json index fd134a5..c8dfaf7 100644 --- a/cmd/observe/ui/dist/.neutron-adapter-static.json +++ b/cmd/observe/ui/dist/.neutron-adapter-static.json @@ -8,8 +8,7 @@ "compression": { "enabled": true, "files": 78, - "gzipBytesSaved": 642496, - "brotliBytesSaved": 703986 - }, - "generatedAt": "2026-09-24T01:37:32.305Z" + "gzipBytesSaved": 642486, + "brotliBytesSaved": 703972 + } } \ No newline at end of file diff --git a/cmd/observe/ui/dist/.neutron-static-policy.json b/cmd/observe/ui/dist/.neutron-static-policy.json index 7740da2..fcd6c72 100644 --- a/cmd/observe/ui/dist/.neutron-static-policy.json +++ b/cmd/observe/ui/dist/.neutron-static-policy.json @@ -1,5 +1,4 @@ { - "generatedAt": "2026-09-24T01:37:31.384Z", "headers": { "/*": { "X-Content-Type-Options": "nosniff", diff --git a/cmd/observe/ui/dist/.neutron-static-policy.json.br b/cmd/observe/ui/dist/.neutron-static-policy.json.br index 3056635..4584864 100644 --- a/cmd/observe/ui/dist/.neutron-static-policy.json.br +++ b/cmd/observe/ui/dist/.neutron-static-policy.json.br @@ -1,2 +1 @@ -€ v,¶f -ÎÝÞ¬ØVªÌE†ED* oø»$ˆH_+€°èXo›�£ÿÂ�¦Ùl&‚’¥¬d-Ðm>]Q¨Œ¹è‘jˆ×.z´tz Z�ÕB”lۉWLµ£ Dç;­{#�]Vj€™A�‡¿–Nçûi»,”5Hd2;Ò,o»)�²xÞH�÷Çm;>@6ƒ]�>hSÈ;8ÔÄ#r9dSüñ~ì?è×ÙS$MÑïä²=Ÿ´­"ï‹EŽXÂmV5fâ \ No newline at end of file +S v,æö<‘JÏÇÎÔtž±šF•‰c�éK�õ¶ 9ú/ühŠ P™"J–²œè6!:<º‚0Y›Ð#Õ¤]Ýem’®Ø Ì©"Iõ5™æ}j«JòÕ7·+£Ä\U“çÅ"³îÇÖæü1„ ’Ó¢ÈîtàPf#Ôf²ÿ:|�éCZ—¾mº¶wͶvO&´Èç"fC•3…­Lêlâ \ No newline at end of file diff --git a/cmd/observe/ui/dist/.neutron-static-policy.json.gz b/cmd/observe/ui/dist/.neutron-static-policy.json.gz index 99ceb4545e219ebc4b7688d38978a7da05f77a56..a47ff267a800652bd71e158e313dfb86e0f0be9a 100644 GIT binary patch literal 211 zcmV;^04)C>iwFP!000026P1olO9Md+K=1o2!W?CSE5#lJPpuwR#DgBwX|@}fk0p~; zOX+`?Ev=>AJOn~`dAwZ%;CiMtYjt<9YXAV;_0{hV;NEdjvT-(i+YXG+gGtE~?Q*GE zy3YAC`lysHnB~LM#~FC#j#XLlr9=ta38D$;{`+a{A_A=wSH z6Sb87yCg)V-aHHp!<)xDL;$KLM{I=38&Nr^RHrM{YfP70o#feNo+anm)$&cnBixWF ziG#}F5CH(F#o5mepk8p}osbjU?t8+=UaWT`t(*_eR@G#g(R()C33Ge*@cIipQ$@_g z_~ct#>_;F7+d|N@t*yh4h8z~`LzpjWdOgN-nF)a;oCa=8(a;phz8#hQuxo7*LuWqG w)O4LC7uiZ{9fR$'); +const dist = resolve(process.argv[2]); +const policyPath = resolve(dist, '.neutron-static-policy.json'); +const metaPath = resolve(dist, '.neutron-adapter-static.json'); +const original = readFileSync(policyPath); +const policy = JSON.parse(original); +const meta = JSON.parse(readFileSync(metaPath)); +delete policy.generatedAt; +delete meta.generatedAt; +const normalized = Buffer.from(JSON.stringify(policy, null, 2)); +for (const [suffix, field, compress] of [ + ['gz', 'gzipBytesSaved', b => gzipSync(b, { level: 9 })], + ['br', 'brotliBytesSaved', b => brotliCompressSync(b, { params: { [constants.BROTLI_PARAM_QUALITY]: 11 } })], +]) { + const previous = readFileSync(`${policyPath}.${suffix}`); + const compressed = compress(normalized); + meta.compression[field] += (normalized.length - compressed.length) - (original.length - previous.length); + writeFileSync(`${policyPath}.${suffix}`, compressed); +} +writeFileSync(policyPath, normalized); +writeFileSync(metaPath, JSON.stringify(meta, null, 2)); diff --git a/scripts/ui-sync.sh b/scripts/ui-sync.sh index 160318f..b7831cc 100755 --- a/scripts/ui-sync.sh +++ b/scripts/ui-sync.sh @@ -59,6 +59,8 @@ find "$CANONICAL_SRC" -type f \( -name '*.ts' -o -name '*.tsx' \) -print0 \ log "pnpm build (canonical)" pnpm -C "$CANONICAL_APP" build +node "$SCRIPT_DIR/normalize-ui-metadata.mjs" "$CANONICAL_APP/dist" + log "replacing embedded dist" rm -rf "$EMBED_DIST" mkdir -p "$EMBED_DIST" From 378ce8dcb5a115472f8fbd93f0594202039e8b1c Mon Sep 17 00:00:00 2001 From: Tyler <53561637+im-tyler@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:36:29 -0700 Subject: [PATCH 4/4] Normalize gzip platform headers and remove ingest fixture startup race --- .../ui/dist/.neutron-static-policy.json.gz | Bin 211 -> 211 bytes cmd/observe/ui/dist/.vite/manifest.json.gz | Bin 2466 -> 2466 bytes .../ui/dist/assets/CodeBlock-eXEjlwde.js.gz | Bin 353 -> 353 bytes .../dist/assets/ConfirmDialog-Dcm8ziSO.js.gz | Bin 373 -> 373 bytes .../ui/dist/assets/EmptyState-Cq8HfmOy.js.gz | Bin 609 -> 609 bytes .../dist/assets/ExportButton-D16qcWq9.js.gz | Bin 609 -> 609 bytes .../ui/dist/assets/LoadError-Cedf2Zdq.js.gz | Bin 446 -> 446 bytes .../ui/dist/assets/Modal-BXflaNUd.js.gz | Bin 747 -> 747 bytes .../ui/dist/assets/Pagination-C2Bjp44k.js.gz | Bin 287 -> 287 bytes .../ui/dist/assets/SearchInput-DG57hjfw.js.gz | Bin 674 -> 674 bytes .../ui/dist/assets/StatusBadge-D4HFP28I.js.gz | Bin 545 -> 545 bytes .../ui/dist/assets/Tabs-HjFjaSVm.js.gz | Bin 506 -> 506 bytes .../ui/dist/assets/_layout-D3Xd7fXB.js.gz | Bin 7582 -> 7582 bytes .../ui/dist/assets/_layout-HPc_Hkdl.css.gz | Bin 3539 -> 3539 bytes .../ui/dist/assets/alerts-X8FEeBv4.js.gz | Bin 3017 -> 3017 bytes .../ui/dist/assets/analytics-CURgGHu0.js.gz | Bin 742 -> 742 bytes .../ui/dist/assets/audit-CgOj4m5w.js.gz | Bin 1402 -> 1402 bytes .../ui/dist/assets/auth-CFVI_Mbd.js.gz | Bin 401 -> 401 bytes .../ui/dist/assets/boards-BaaRYrYm.css.gz | Bin 981 -> 981 bytes .../ui/dist/assets/boards-BtLKgPKd.js.gz | Bin 2705 -> 2705 bytes .../ui/dist/assets/campaigns-Z-BMMaUY.js.gz | Bin 2696 -> 2696 bytes .../ui/dist/assets/clipboard-BRysZGwJ.js.gz | Bin 259 -> 259 bytes .../ui/dist/assets/cohorts-5PmHEjPf.js.gz | Bin 3446 -> 3446 bytes .../ui/dist/assets/dashboard-Bu3tsvbK.css.gz | Bin 3509 -> 3509 bytes .../ui/dist/assets/dashboards-CMWV5B4P.js.gz | Bin 6399 -> 6399 bytes .../ui/dist/assets/dashboards-TvcfB7ZH.css.gz | Bin 1087 -> 1087 bytes .../ui/dist/assets/docs-DtkLZ6TH.js.gz | Bin 7157 -> 7157 bytes .../ui/dist/assets/docs-jX5Zsbxb.css.gz | Bin 923 -> 923 bytes .../ui/dist/assets/errors-CabbRYx0.js.gz | Bin 4507 -> 4507 bytes .../ui/dist/assets/errors-Cec7liwy.js.gz | Bin 397 -> 397 bytes .../ui/dist/assets/errors-R5Ijq7C9.css.gz | Bin 2078 -> 2078 bytes .../ui/dist/assets/events-B4JfCITX.js.gz | Bin 2283 -> 2283 bytes .../ui/dist/assets/experiments-CBaekaSK.js.gz | Bin 3356 -> 3356 bytes .../ui/dist/assets/explorer-Dcjw3AWf.js.gz | Bin 1725 -> 1725 bytes .../ui/dist/assets/explorer-ifCJQ3BP.css.gz | Bin 785 -> 785 bytes .../ui/dist/assets/flags-B78yBvO7.js.gz | Bin 3572 -> 3572 bytes .../ui/dist/assets/flags-CE0lBMCA.js.gz | Bin 286 -> 286 bytes .../ui/dist/assets/flags-ClqmhoJ5.css.gz | Bin 1402 -> 1402 bytes .../ui/dist/assets/helpers-VROGXNpX.js.gz | Bin 1189 -> 1189 bytes .../ui/dist/assets/hooks-ijpLJSlR.js.gz | Bin 1263 -> 1263 bytes .../ui/dist/assets/hydrate-24_12g4P.js.gz | Bin 6554 -> 6554 bytes .../ui/dist/assets/incidents-Bwvs4ZlH.js.gz | Bin 1935 -> 1935 bytes .../ui/dist/assets/index-CQlltJUM.js.gz | Bin 50472 -> 50472 bytes .../ui/dist/assets/index-DLhXU6fD.js.gz | Bin 8675 -> 8675 bytes .../ui/dist/assets/insights-BKICK4sl.js.gz | Bin 8118 -> 8118 bytes .../ui/dist/assets/insights-Cic7Ai_u.css.gz | Bin 1605 -> 1605 bytes .../dist/assets/integrations-il7Uwk8Z.js.gz | Bin 2875 -> 2875 bytes .../ui/dist/assets/jsxRuntime-BVeGbjfr.js.gz | Bin 297 -> 297 bytes cmd/observe/ui/dist/assets/llm-CKAOJT3f.js.gz | Bin 2457 -> 2457 bytes .../ui/dist/assets/login-CQUvaGga.js.gz | Bin 1843 -> 1843 bytes .../ui/dist/assets/logs-BCixbtxU.css.gz | Bin 1580 -> 1580 bytes .../ui/dist/assets/logs-CcuXv9IZ.js.gz | Bin 3547 -> 3547 bytes .../ui/dist/assets/meta-CvRzEPy0.js.gz | Bin 986 -> 986 bytes .../ui/dist/assets/meta-Dhn6Xpef.css.gz | Bin 612 -> 612 bytes .../ui/dist/assets/metrics-BhMUdMNZ.js.gz | Bin 366 -> 366 bytes .../ui/dist/assets/metrics-C1dSKLn-.js.gz | Bin 3211 -> 3211 bytes .../ui/dist/assets/monitoring-BQgKlspP.js.gz | Bin 3863 -> 3863 bytes .../ui/dist/assets/monitoring-BRTRhWU8.css.gz | Bin 1236 -> 1236 bytes .../ui/dist/assets/onboard-C8Yvwrnx.js.gz | Bin 3071 -> 3071 bytes .../ui/dist/assets/onboard-DZ_JFrT5.css.gz | Bin 1381 -> 1381 bytes .../ui/dist/assets/persons-B_VByFqa.js.gz | Bin 530 -> 530 bytes .../ui/dist/assets/persons-DvXNJJBL.js.gz | Bin 2465 -> 2465 bytes .../ui/dist/assets/releases-BXFoJ3wM.js.gz | Bin 1929 -> 1929 bytes .../ui/dist/assets/reports-pOpVNGOC.js.gz | Bin 1750 -> 1750 bytes .../ui/dist/assets/sessions-1nnFrpY_.js.gz | Bin 8192 -> 8192 bytes .../ui/dist/assets/sessions-D_rwxSji.css.gz | Bin 1885 -> 1885 bytes .../ui/dist/assets/settings-BjDXM5Tx.js.gz | Bin 374 -> 374 bytes .../ui/dist/assets/settings-D8_XUN_q.css.gz | Bin 885 -> 885 bytes .../ui/dist/assets/settings-hOmVp-ct.js.gz | Bin 9357 -> 9357 bytes .../ui/dist/assets/setup-XwZxYwSR.js.gz | Bin 920 -> 920 bytes .../ui/dist/assets/surveys-3ecQbmSx.js.gz | Bin 2465 -> 2465 bytes .../ui/dist/assets/traces-A5dZarWK.js.gz | Bin 11408 -> 11408 bytes .../ui/dist/assets/traces-_fezYNMq.css.gz | Bin 1985 -> 1985 bytes .../ui/dist/assets/useFilters-BPIsTX3T.js.gz | Bin 1545 -> 1545 bytes cmd/observe/ui/dist/favicon.svg.gz | Bin 288 -> 288 bytes cmd/observe/ui/dist/index.html.gz | Bin 270 -> 270 bytes cmd/observe/ui/dist/rrweb/replayer.js.gz | Bin 82378 -> 82378 bytes cmd/observe/ui/dist/rrweb/sanitize.js.gz | Bin 2397 -> 2397 bytes internal/ingest/buffer_test.go | 7 ++++--- scripts/normalize-ui-metadata.mjs | 18 +++++++++++++++++- 80 files changed, 21 insertions(+), 4 deletions(-) diff --git a/cmd/observe/ui/dist/.neutron-static-policy.json.gz b/cmd/observe/ui/dist/.neutron-static-policy.json.gz index a47ff267a800652bd71e158e313dfb86e0f0be9a..434895948dbaef42e177e937b19bb9e1cf47b9c9 100644 GIT binary patch delta 18 Xcmcc2c$tw)zMF#q1epF$aCZ diff --git a/cmd/observe/ui/dist/.vite/manifest.json.gz b/cmd/observe/ui/dist/.vite/manifest.json.gz index d62cd43f18a7b498bdc0212d0ecd10f2ab0f6e95..e070bb18fe849e0c2bb6447afb6a38b7a68d44f1 100644 GIT binary patch delta 19 YcmZ1^yhxZ!zMF#q1epGBi05vTIJOBUy delta 19 YcmaFO`kIwXzMF#q1ek<3ay?`M05FjRPyhe` diff --git a/cmd/observe/ui/dist/assets/Pagination-C2Bjp44k.js.gz b/cmd/observe/ui/dist/assets/Pagination-C2Bjp44k.js.gz index d69607e59d18f47af9a8dbeab5d0135c7cbb5c3b..266a4b137346b4fcd2e6910a9b527003574597e5 100644 GIT binary patch delta 19 YcmbQwG@pq}zMF#q1epGBrazMF#q1epGBrazMF#q1ek<3a?M}@042KvbpQYW diff --git a/cmd/observe/ui/dist/assets/StatusBadge-D4HFP28I.js.gz b/cmd/observe/ui/dist/assets/StatusBadge-D4HFP28I.js.gz index a40a89cd5075c4c510d5f2ee037d76c905251769..23eec8229cd7b42495cc6bf00c2653de909ba816 100644 GIT binary patch delta 19 YcmZ3;vXF&KzMF#q1epGBq)@Bjb+ diff --git a/cmd/observe/ui/dist/assets/Tabs-HjFjaSVm.js.gz b/cmd/observe/ui/dist/assets/Tabs-HjFjaSVm.js.gz index 1ec4529c34c9c6368d985a1848c826011c478e81..742f43d6764600af787a8f06624c824657bfbd0e 100644 GIT binary patch delta 19 Ycmeyx{EL}OzMF#q1epGB89WB>pF delta 19 Ycmeyx{EL}OzMF#q1ek<3a(!R~05XOIcmMzZ diff --git a/cmd/observe/ui/dist/assets/_layout-D3Xd7fXB.js.gz b/cmd/observe/ui/dist/assets/_layout-D3Xd7fXB.js.gz index a044ab64ffc4ddb21c49e65457c8f7c7a399eb7c..0b7e924395d0a1485101205d0d9647df6d92dda8 100644 GIT binary patch delta 19 YcmbPdJpeo~xEzMF#q1epGBpeo~xEzMF#q1ek<3a_#2^04?$a7XSbN diff --git a/cmd/observe/ui/dist/assets/analytics-CURgGHu0.js.gz b/cmd/observe/ui/dist/assets/analytics-CURgGHu0.js.gz index a02f825907ddf917ed9b139b9c39f8a20fcd4ee0..6d89b6a482cfad8463e09385d547347641496bfb 100644 GIT binary patch delta 19 YcmaFH`izxJzMF#q1epGBz>% diff --git a/cmd/observe/ui/dist/assets/audit-CgOj4m5w.js.gz b/cmd/observe/ui/dist/assets/audit-CgOj4m5w.js.gz index dd2d8b3815f08318102ea3de8a2b91d9b28d4d4e..56eafa0705fca092ed620a78b5576d17df3f5a30 100644 GIT binary patch delta 19 Ycmeyx^^1#3zMF#q1epGBOx$3w8D5C^B delta 19 XcmeAW?GWXX@8)0t0Vd&%TyYG&e+@8)0t0jB>GxqbowA$bH` delta 18 WcmZo>YG&e+@8)0t0Vd&zTt5LF$pdfz diff --git a/cmd/observe/ui/dist/assets/cohorts-5PmHEjPf.js.gz b/cmd/observe/ui/dist/assets/cohorts-5PmHEjPf.js.gz index 25fb8e9bd9c81050d2d0811c5add4d1eb01aaf4f..0f7484ddc42737f659eb4aa72a469f2413ab5c78 100644 GIT binary patch delta 19 Ycmew+^-YRPzMF#q1epGB&>05k{$CIA2c diff --git a/cmd/observe/ui/dist/assets/dashboard-Bu3tsvbK.css.gz b/cmd/observe/ui/dist/assets/dashboard-Bu3tsvbK.css.gz index b0f25b5b1e96db7eb5c7eac1395cc85c919c806c..7368f5b724f2f5e98b424a69f860635eae562c5a 100644 GIT binary patch delta 19 Ycmdlgy;Yh^zMF#q1epGBi_@% diff --git a/cmd/observe/ui/dist/assets/docs-jX5Zsbxb.css.gz b/cmd/observe/ui/dist/assets/docs-jX5Zsbxb.css.gz index 8c60b4c5bab1c9df0e9af9c307c339159d3e694e..e1f76f6aa7ee1483f8946e20986dd37c6ddd788b 100644 GIT binary patch delta 19 YcmbQuKAW9OzMF#q1epGBOxtbXPCiDay delta 19 XcmeBW?q%kZ@8)0t0Vd&%T+NICB3J`4 diff --git a/cmd/observe/ui/dist/assets/errors-R5Ijq7C9.css.gz b/cmd/observe/ui/dist/assets/errors-R5Ijq7C9.css.gz index 9bee18e083530adc4cf2a3eb6557ff6427b92575..4aa4f8cb9d41c8f6a44343049667a9b6823e10f6 100644 GIT binary patch delta 19 YcmbOyFi(I>zMF#q1epGBzMF#q1ek<3a*1#N041LT1poj5 diff --git a/cmd/observe/ui/dist/assets/events-B4JfCITX.js.gz b/cmd/observe/ui/dist/assets/events-B4JfCITX.js.gz index 7450841adf8c20288841644e014f889e24261c97..4e6133ced8a60db1cf047bf63c03fd4cf06a2382 100644 GIT binary patch delta 19 YcmaDY_*#%lzMF#q1epGBf05;17S^xk5 delta 19 YcmaDY_*#%lzMF#q1ek<3ay{e#05UHGZU6uP diff --git a/cmd/observe/ui/dist/assets/experiments-CBaekaSK.js.gz b/cmd/observe/ui/dist/assets/experiments-CBaekaSK.js.gz index be3c98f21dff565bbc970057e3d2e194928d8757..be9618e1761ac6cd5d876d2cd6faf1a45000c8e1 100644 GIT binary patch delta 19 YcmbOuHAjj|zMF#q1epGB#sB~S delta 19 YcmdnXyO)zMF#q1epGBb%7 delta 19 YcmbQpHj#}>zMF#q1ek<3aR diff --git a/cmd/observe/ui/dist/assets/flags-B78yBvO7.js.gz b/cmd/observe/ui/dist/assets/flags-B78yBvO7.js.gz index 18d9e3f35aa9b751cda541ba3615d85a92c59fbc..df2e345cae2f0c820b8d6d64de78b41dee4383f9 100644 GIT binary patch delta 19 Ycmew&{Y9EfzMF#q1epGB?f(zMF#q1epGB704P`k&Hw-a delta 19 YcmbQoG>?f(zMF#q1ek<3a)~ek03*Bt;s5{u diff --git a/cmd/observe/ui/dist/assets/flags-ClqmhoJ5.css.gz b/cmd/observe/ui/dist/assets/flags-ClqmhoJ5.css.gz index 6237ea45200a96b6cf6e60ec2e717ab4b2babb85..56da5e47aa289a7e7eb60d501fdbd4f19a1a056b 100644 GIT binary patch delta 19 Ycmeyx^^1#3zMF#q1epGBOxmwu)DBc7? delta 19 XcmeC@@8{=|@8)0t0Vd&%T&?T?BtipK diff --git a/cmd/observe/ui/dist/assets/index-CQlltJUM.js.gz b/cmd/observe/ui/dist/assets/index-CQlltJUM.js.gz index e7aa82bdccf860f22dadaac4a14a59658a7fe534..a5b206d24014d0e212ba33a98c4ba8e739386285 100644 GIT binary patch delta 21 acmZ3{#k``6nM=N#g8>AX{%_=xI|=|msRhOW delta 21 acmZ3{#k``6nM=N#g8>AXgg0`@9R&b94FuW% diff --git a/cmd/observe/ui/dist/assets/index-DLhXU6fD.js.gz b/cmd/observe/ui/dist/assets/index-DLhXU6fD.js.gz index 8c9497b5c36981cffd8215958f31b294ce53c9cb..571080fae22db12ca08e3ef3e70ffe3afb5ef575 100644 GIT binary patch delta 19 YcmaFt{MeaGzMF#q1epGB5E&u=k delta 19 YcmdmHzs;UYzMF#q1ek<3a;=dE05F6ELI3~& diff --git a/cmd/observe/ui/dist/assets/insights-Cic7Ai_u.css.gz b/cmd/observe/ui/dist/assets/insights-Cic7Ai_u.css.gz index fd0360f7ea02fc9e323b31406b2acc250be3de08..ad11042e94e3daff88a74e1b156770d593ac1210 100644 GIT binary patch delta 19 YcmX@gbCicmzMF#q1epGB+9S03}`n0{{R3 diff --git a/cmd/observe/ui/dist/assets/llm-CKAOJT3f.js.gz b/cmd/observe/ui/dist/assets/llm-CKAOJT3f.js.gz index daf0f5e297704b87b22e6b14fbc987768487a95a..ceb771121f3b757c7e1c26286748243e7ceb282e 100644 GIT binary patch delta 19 YcmbO!JX4rUzMF#q1epGB*D*ylh delta 19 YcmdnYx0#PizMF#q1ek<3a%r*y04R6^KL7v# diff --git a/cmd/observe/ui/dist/assets/logs-BCixbtxU.css.gz b/cmd/observe/ui/dist/assets/logs-BCixbtxU.css.gz index e6288c6070eb5d6a212210c64754f420dcb7709e..000840dcdd7dcfaea302adfc69844dedf13d0ca1 100644 GIT binary patch delta 19 YcmZ3(vxbLDzMF#q1epGB(^b diff --git a/cmd/observe/ui/dist/assets/logs-CcuXv9IZ.js.gz b/cmd/observe/ui/dist/assets/logs-CcuXv9IZ.js.gz index 16a80b35da50edae85036f9ffb142aef292dadec..52c9f1f3da6833b5164d9bab44e052297fab6c7f 100644 GIT binary patch delta 19 YcmcaDeOsDKzMF#q1epGB8R{#J2 diff --git a/cmd/observe/ui/dist/assets/meta-CvRzEPy0.js.gz b/cmd/observe/ui/dist/assets/meta-CvRzEPy0.js.gz index b4a90f3f3aa56b229502c8a62aac27db3f5feac9..97e29c48d120d6c631c5171ce4c7942d81c23de3 100644 GIT binary patch delta 19 Ycmcb`ev6$;zMF#q1epGBOxf*!@DW3#R delta 19 XcmeB{?3Uz`@8)0t0Vd&%T#Y;cB?ALu diff --git a/cmd/observe/ui/dist/assets/monitoring-BQgKlspP.js.gz b/cmd/observe/ui/dist/assets/monitoring-BQgKlspP.js.gz index 3d5a3228043c029aab25ef1554427ddef206e5c0..d095c895a0375453ab5eceda821856d0cef163da 100644 GIT binary patch delta 19 YcmbO(H(icPzMF#q1epGBh($ diff --git a/cmd/observe/ui/dist/assets/releases-BXFoJ3wM.js.gz b/cmd/observe/ui/dist/assets/releases-BXFoJ3wM.js.gz index 04926e0997cb985a9bfe165b5e0e06bb8c2b8ba5..5a618e03ad8a1836b9e25774a57ec52feda2d0bd 100644 GIT binary patch delta 19 XcmeC=@8su_@8)0t0jB>Ox$4;gC;9|0 delta 19 XcmeC=@8su_@8)0t0Vd&%T=nb#BVGeT diff --git a/cmd/observe/ui/dist/assets/reports-pOpVNGOC.js.gz b/cmd/observe/ui/dist/assets/reports-pOpVNGOC.js.gz index 39a7e7570bf0baf7606b818d29df5265ae5b73a3..89ad626b6c1c532463529336d82e1d6f73d42874 100644 GIT binary patch delta 19 Ycmcb{dySV%zMF#q1epGB(^b diff --git a/cmd/observe/ui/dist/assets/sessions-1nnFrpY_.js.gz b/cmd/observe/ui/dist/assets/sessions-1nnFrpY_.js.gz index 0ef94545edd1def867b6b62b9b7ad7f784ba598d..2e867e4171ad7c1a5eb3b3704635b6284828d202 100644 GIT binary patch delta 19 YcmZp0XmH??@8)0t0jB>OxxUE*04_cS4gdfE delta 19 XcmZp0XmH??@8)0t0Vd&%T;JpYDWU`- diff --git a/cmd/observe/ui/dist/assets/sessions-D_rwxSji.css.gz b/cmd/observe/ui/dist/assets/sessions-D_rwxSji.css.gz index 2353c2d8af067895149bf7cbf4b08c43ea3aa515..e3f2b4f3f2dd6f794e5aff2b6fef0beb1203f37b 100644 GIT binary patch delta 19 Ycmcc1cbAV#zMF#q1epGB@~ delta 19 Ycmcc1cbAV#zMF#q1ek<3as{&k04}rxy#N3J diff --git a/cmd/observe/ui/dist/assets/settings-BjDXM5Tx.js.gz b/cmd/observe/ui/dist/assets/settings-BjDXM5Tx.js.gz index 39233e59ba57f67b99bcec655eddd8ca6ea9810c..d4c31889eb9135ec545dca7c918291f578b5ac11 100644 GIT binary patch delta 19 Ycmeyy^o@y2zMF#q1epGBHq)$ diff --git a/cmd/observe/ui/dist/assets/settings-D8_XUN_q.css.gz b/cmd/observe/ui/dist/assets/settings-D8_XUN_q.css.gz index 7cbe0c81eeb14131394f7dce18f24356944152bc..eae7d09ae63eab143677eadb4f24d5a493436d94 100644 GIT binary patch delta 19 Ycmey$_LYrGzMF#q1epGBOxtdh~Fk=MF delta 19 XcmeD6?Dgc5@8)0t0Vd&%T+J!~E5`%i diff --git a/cmd/observe/ui/dist/assets/setup-XwZxYwSR.js.gz b/cmd/observe/ui/dist/assets/setup-XwZxYwSR.js.gz index 49dfc6874299a811293ebeec52ea149ce2c76f6d..319be376859f9b7e88853c371f47be91c0f0cf60 100644 GIT binary patch delta 19 YcmbQiK7*Z0zMF#q1epGBh($ diff --git a/cmd/observe/ui/dist/assets/traces-A5dZarWK.js.gz b/cmd/observe/ui/dist/assets/traces-A5dZarWK.js.gz index 9b330cb5ada7473573f4271f99944d9b4dcd47ff..9e4a39fe935a1cead81d27692fd5c084908eb4cd 100644 GIT binary patch delta 19 YcmbObIU$luzMF#q1epGB-05D1f*8l(j delta 19 YcmX@ee~_O`zMF#q1ek<3a&2b^04uHo>i_@% diff --git a/cmd/observe/ui/dist/assets/useFilters-BPIsTX3T.js.gz b/cmd/observe/ui/dist/assets/useFilters-BPIsTX3T.js.gz index 80c9d7599b048f82bcfe4c12a2002f8781a7ab31..ecd19f1aac19b0bb3adffc10fd0bdab57b73e4f9 100644 GIT binary patch delta 19 YcmeC=>Ez*(@8)0t0jB>Ox&E^P04S0KrvLx| delta 19 XcmeC=>Ez*(@8)0t0Vd&%T>n`CBlZKk diff --git a/cmd/observe/ui/dist/favicon.svg.gz b/cmd/observe/ui/dist/favicon.svg.gz index a613758c3a132bbb2797f4ab5967e5c2ec2028f5..9066aec9928df6ec240549d2ff25c62d647a5c21 100644 GIT binary patch delta 19 YcmZ3$w1A0AzMF#q1epGBSN-P@8)0t0jB>OxmXwhCHMoL delta 19 XcmeBU>SN-P@8)0t0Vd&%Tr7+LAzT8o diff --git a/cmd/observe/ui/dist/rrweb/replayer.js.gz b/cmd/observe/ui/dist/rrweb/replayer.js.gz index 77bd9cdd35a10bcccbf491e804422167031d6f56..15f09165eff592ac215c243dbff259bf5d3585d0 100644 GIT binary patch delta 24 dcmX@r%zCPsl}o;xg8>AX{x@=MAXgd4fGaxoro1OQMq1;PLT diff --git a/cmd/observe/ui/dist/rrweb/sanitize.js.gz b/cmd/observe/ui/dist/rrweb/sanitize.js.gz index 81bd57fe5efd627b981007977e09a8429daaac89..62a0a6a81f37943b0547c381062e565a309f6fcc 100644 GIT binary patch delta 19 YcmcaBbXSN=zMF#q1epGB insertBatch panics inside Flush, recovered by the worker's // panic guard, leaving the events requeued... in fact the panic aborts // the whole worker goroutine (recover logs and exits the goroutine), diff --git a/scripts/normalize-ui-metadata.mjs b/scripts/normalize-ui-metadata.mjs index c884493..5a5fcb9 100644 --- a/scripts/normalize-ui-metadata.mjs +++ b/scripts/normalize-ui-metadata.mjs @@ -1,7 +1,7 @@ // Embedding does not need build wall-clock timestamps. Neutron currently emits // them unconditionally (reported in Teploy/_internal/UPSTREAM_BUGS.md). // Preserve policy content and accurate compression counts while removing time. -import { readFileSync, writeFileSync } from 'node:fs'; +import { readFileSync, writeFileSync, readdirSync } from 'node:fs'; import { resolve } from 'node:path'; import { gzipSync, brotliCompressSync, constants } from 'node:zlib'; @@ -26,3 +26,19 @@ for (const [suffix, field, compress] of [ } writeFileSync(policyPath, normalized); writeFileSync(metaPath, JSON.stringify(meta, null, 2)); + +// gzip's OS header differs on macOS/Linux despite identical deflate payloads. +// RFC 1952 value 255 means unknown OS; this byte is outside the payload CRC. +function normalizeGzipHeaders(dir) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = resolve(dir, entry.name); + if (entry.isDirectory()) normalizeGzipHeaders(path); + else if (entry.name.endsWith('.gz')) { + const bytes = readFileSync(path); + if (bytes[0] !== 0x1f || bytes[1] !== 0x8b || bytes[3] !== 0) throw new Error(`Unexpected gzip header: ${path}`); + bytes[9] = 255; + writeFileSync(path, bytes); + } + } +} +normalizeGzipHeaders(dist);