chore(deps): bump typescript from 5.9.3 to 7.0.2 in /site - #199
Closed
dependabot[bot] wants to merge 47 commits into
Closed
chore(deps): bump typescript from 5.9.3 to 7.0.2 in /site#199dependabot[bot] wants to merge 47 commits into
dependabot[bot] wants to merge 47 commits into
Conversation
Fanout: single-binary OpenTelemetry investigation. One Go process owns OTLP ingest, DuckLake/Parquet storage, DuckDB queries, MCP, the agent runtime, and the embedded React client. Licensed under AGPL-3.0.
Build ordering was previously prose in the README: the browser workspaces write into internal/ui/dist and internal/mcp/apps, both committed and compiled in via go:embed, so the binary is only as fresh as the last UI build. The justfile encodes that ordering, and `just check` is the single gate that lefthook's pre-push hook and CI both run. `just ui-check` guards the embedded assets against drifting from their source. It builds both workspaces and byte-compares the result against the committed bytes, using git as its backup: it refuses to run when those paths are already dirty, and always restores them, so it never mutates the working tree. This is the check that was missing when the assets last went stale. Hooks are split by cost — formatting and linting on commit, the full gate on push, where the UI rebuild and test suite are worth their minute. Action and tool versions are pinned exactly rather than floating. A bun upgrade can change bundle bytes, which would trip the drift check from a CI run rather than a deliberate commit. openspec/ ships scaffolded but empty; `just spec-check` validates it and currently passes vacuously until specs land. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A bare `testdata/` rule ignored every Go package's fixture directory, not just a root-level scratch dir. internal/brand's test reads a vendored testdata/favicon.svg, so the file existed on the machine that wrote the test but was never committed, and `go test ./...` failed on any fresh clone. Local runs passed only because the untracked file was present. Scope the rule to /testdata/ and commit the fixture. Go treats testdata/ as source, so package fixtures must be tracked. Found by the first CI run on a clean checkout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The README read like an internal note: a bare layout table, three manual build commands, and no statement of what Fanout is or why the single-binary shape matters. Replace it with a structure a first-time visitor can act on — what it does, how it fits together, how to run it, and how to develop on it. Add the runtime architecture and persistence diagrams, authored in d2 and rendered to committed SVG, plus a `diagrams` recipe to regenerate them. The SVG is committed so reading the repo never requires d2. Document the configuration honestly: Fanout does not start unconfigured. Local auth requires SMTP and a 32-character AUTH_CODE_SECRET, OIDC requires a provider, and the agent requires AI_API_KEY. The quick start lists the six variables that were verified to boot the server. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The repository claimed to be "opened up incrementally" while omitting the one
exclusion that actually cost users: there was no Dockerfile, so a self-hosted
observability tool had no deployment path at all. Restore the multi-stage
build and publish ghcr.io/labstack/fanout from CI on main and on tags.
Harden the image over the version it came from: it runs unprivileged as a
system user, carries OCI labels linking it back to this repository, and has a
HEALTHCHECK. The healthcheck uses GET rather than wget --spider, because
/healthz rejects HEAD with 405 and a spider probe reports a healthy container
as unhealthy — confirmed by running it.
Releases are CalVer, v{YYYY.MM}.{N}. The metadata action matches the tag
literally rather than parsing it as semver, which mangles a leading zero like
v2026.04.1.
Add SECURITY.md and CONTRIBUTING.md; the README invited contributions with no
disclosure path and no setup guidance.
Replace the Status section with Scope. A fresh clone builds a complete working
binary, so "opened up incrementally" misdescribed the repository: what is
withheld is LabStack's operation of Fanout, not part of Fanout.
Drop dead configuration: golangci-lint exclusions for two files that do not
exist, .gitignore entries for site/, traefik/, and binaries with no packages.
Narrow the blanket *.png rule to the root for the same reason testdata/ was
narrowed — it would silently swallow committed image assets. Repoint four
comments that referenced site/ and scripts/ at files this repository has.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fanout is optimizing for adoption rather than for preventing third parties from hosting it, and AGPL costs adoption: several large companies ban it outright, so a tool meant to run inside their infrastructure never gets evaluated. Apache-2.0 removes that barrier. Relicensing is clean right now and will not stay that way. Every commit is authored by v@labstack.com and there are no external contributors, so LabStack owns all of it. Add a CLA before that changes: it grants the right to relicense, which is the only thing that keeps future licensing decisions open. Without it, Apache-2.0 is a one-way door, since altering terms later would need permission from every past contributor and one unreachable person could block it permanently. Nothing in the dependency tree constrained the choice. All 62 modules linked into the binary are MIT, BSD, Apache-2.0, or MPL-2.0, with no GPL, LGPL, or AGPL anywhere. Signatures are stored on an orphan cla-signatures branch rather than main, so signing does not add bot commits to release history or trigger an image publish. The workflow never checks out pull request code, because pull_request_target runs with a writable token in the base repository. Add NOTICE for attribution, as is conventional under Apache-2.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The README claimed Fanout replaces a fleet of services with one process and
offered no evidence of what that process sustains. Measuring it exposed that
cmd/bench could not answer the question: it took a fixed -rate and -workers,
and the adaptivity that sized them lived in scripts/, which is not part of
this repository. Hand-picking a rate measures the guess.
cmd/bench now sizes senders from the driver's cores, ramps until the server
stops keeping up, bisects to find the boundary, and confirms at that rate.
Three details were forced by measurement rather than designed up front:
- Bisecting, not stopping at the first miss. A doubling ramp that halts on
one noisy step reported 1991/s and 3946/s for the same machine minutes
apart.
- A 0.95 delivery floor. At 0.90, a step delivering 90.8% was called
sustainable at 7267/s and the confirmation held 6503/s.
- Reporting the lower of ramp estimate and confirmed rate. Capacity is not
stationary: rollup cost grows with the dataset, so a ramp can measure a
machine whose capacity is falling underneath it.
Remove the built-in 1500ms query SLO. A fixed millisecond threshold encodes
the machine it was calibrated on, so the same binary reports a defect on two
cores where there is only a smaller computer. Every integrity failure stays:
dropped rows, a mid-run restart, a lost baseline, and an excessive send-error
rate still fail hard, because those make the numbers untrustworthy rather
than merely unflattering.
Results on two dedicated vCPUs: 5,138 traces/s (22,612 rows/s), export p95
3ms, zero dropped. Five queries/s costs 47% of that ingest capacity. The
report also records what the runs exposed operationally — an OOM kill at
7.5GB RSS with DUCKDB_MEMORY unset, and backfill sustaining 832 traces/s
against 5,138 while producing 9.37GB on disk for ~660k rows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lefthook skips pre-push jobs when its changed-file detection returns an empty set, which it did on some pushes: the gate reported 0.03s and ran nothing, while other pushes ran the full 13s. An intermittently-enforced gate is worse than an honest one, because a green push stops meaning anything. Document it as the local convenience it is. CI runs the same just check unconditionally and is what actually enforces the gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fanout's default configuration could be killed by the kernel. On a 2 vCPU / 8 GB host the process was OOM-killed at 7.5 GB RSS: Out of memory: Killed process (fanout) anon-rss:7566436kB DUCKDB_MEMORY was unset by default, leaving DuckDB's own self-sizing in place: 80% of detected memory, about 6.2 GB there. That budget is computed as though DuckDB owned the machine, but it is a library inside a Go process whose heap, stacks, and GC headroom sit on top. 80% plus the runtime exceeds 100% and the kernel settles the difference. The README's Docker quick start reproduces this on any host where that sum exceeds the box. Resolve it instead from detected memory, reserving 40% for the runtime, and detect cgroup-first because a container limit is what the kernel actually enforces — reading the host's total from inside a constrained container is the same class of mistake. A --memory 6g container reports 6442450944 and now resolves to 3686MB, against the 5.2 GB that failed. When no source is conclusive, leave the value empty and warn rather than guess: capping an unknown machine trades a hypothetical crash for a certain slowdown. DUCKDB_MAX_CONNS was fixed at 4 regardless of the machine — four concurrent queries oversubscribing two cores, and an artificial ceiling on thirty-two. It now scales with cores, floored at 2 because internal/lake rejects a pool above 1 without a write gate, and capped at 16 where connections stop adding concurrency. FLUSH_BATCH_SIZE is deliberately untouched. The appender benchmark shows batch size matters, but not how it trades against Parquet file count and query latency, and that measurement has not been made. Adaptive defaults cost reproducibility, so resolution is logged at startup with each value marked auto or pinned. Nothing here overrides an explicitly set variable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fix runtime sizing and benchmark convergence * Make runtime sizing transparent across platforms
* Require authenticated demo access * Fix adaptive ramp latency baseline
* Add layered runtime configuration * Harden runtime configuration loading * Finalize namespaced configuration contract
resolveUser returned as soon as a linked (issuer, subject) was found, so allowed groups, allowed domains, and group-to-role mappings were only ever evaluated at first login. Removing a user from an allowed IdP group did not prevent their next login, and removing them from an admin group did not drop their local admin role: configuration that reads as ongoing policy behaved as first-login provisioning policy. Linked logins now re-evaluate the configured allow policy against the claims presented now, including the email used for domain matching. When a group-to- role mapping is configured the IdP owns the fixed role; the role change, auth version increment, session revocation, and audit event are one transaction via UpdateWithAudit. With no allow policy or no role mapping configured, locally managed membership and roles are left alone. The last active administrator is never demoted, so an installation cannot lose the ability to administer itself. Entra ID group overage is handled explicitly: past roughly 200 memberships the groups claim is omitted and _claim_names names a Graph API source instead. Reading that absence as an empty group set would demote or lock out an entire directory at once, so a token signalling overage is denied with an error that names the remediation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first-administrator credential carried 48 bits and was cleared only at the end of a successful setup request, after fallible ingest work. The handler also established a session for an existing admin on ErrSetupComplete, so one credential could mint several sessions. - Generate 128 bits and deliver them inside the setup URL rather than as a dash-grouped code. PR #82 reduced entropy for typing convenience, but the credential is pasted alongside a URL, so length costs nothing in usability. The login screen reads setup_token from the query string and strips it from the address bar so it does not linger in history or a shared screen. - Consume the credential immediately after the first administrator commits, before any other work. A later failure in that request no longer leaves a live administrator credential behind. - Deny ErrSetupComplete with 403 and establish no session. - Warn in the startup banner that the URL is a credential and that stderr may be collected by container logs and log shippers. Denying the retry path removes the only route back in for an installation whose session establishment fails after commit, so docs/authentication.md documents the recovery procedure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verify read the attempt count, compared it against the limit, and incremented in a separate statement. Concurrent requests could all observe a count below the limit and evaluate more guesses than the three the code advertises; a 32-way concurrent test evaluated 18. Each guess now reserves its attempt with a conditional update and checks the affected row count before the code is compared. Single-use behaviour for a correct code is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
POST /oauth/register validated its input but had no keyed limiter, so an attacker could create an arbitrary burst of clients before the janitor collected the abandoned ones. Registration is now bounded per source. Registration is also unauthenticated by design, which means client_name on the consent screen is attacker-chosen: a client registered under a trusted-sounding name is indistinguishable from a legitimate one. The consent card now marks dynamically registered clients as unverified and emphasises the redirect host, which the name cannot forge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers the first-run setup URL and its log-exposure risk, the login paths that must exist before setup completes, and a three-step recovery procedure for a locked-out installation. Documents the trusted-proxy configuration that rate limiting and audit attribution depend on: forwarded client addresses are ignored by default, and FANOUT_TRUSTED_PROXY_CIDRS becomes the complete trust set when configured. Documents the OIDC behaviour that changed in this branch: eligibility and role mapping are reconciled on every login, the last active administrator is never demoted, Entra ID group overage is denied with its remediation, and email verification defaults to required. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review of the preceding four commits found the reconciliation work applied unevenly and the token-shape handling too narrow. - Enforce the allow policy on the linking login, not only on later ones. An administrator-created local account could link an identity that the policy rejects and then be denied on every subsequent login. This replaces the issuer-mode-only check, so one code path now decides eligibility. - Report an unusable email claim as such. Normalizing a missing or malformed claim to an empty string turned a provider misconfiguration into "identity no longer satisfies the allow policy" for every user at once, naming the wrong cause. The group allow list is still evaluated first, so an unusable email only matters when a domain policy has to decide. - Detect the hasgroups form of Entra group overage in addition to _claim_names, and decode both as raw JSON. A provider emitting an unexpected shape for either previously failed the whole token unmarshal and denied every login. - Skip role reconciliation for a deactivated account. The caller denies it, but reconciliation had already rewritten the stored role, revoked sessions, and written a role.changed event on every rejected attempt. - Read the setup token from the URL purely and strip it in an effect. Doing both in the useState initializer meant a re-invoked initializer under StrictMode read the already-stripped URL and dropped the credential. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A local smoke test showed configuration validation already refuses to start without SMTP in local mode, or without an issuer and client in OIDC mode, so the documented scenario of an installation with neither was wrong. The real risk is a login path that is configured but not working, which is what the recovery procedure addresses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…isioning Second review of the branch found reconciliation still applied unevenly. - Reconcile the role on the linking login, not only on later ones. A local account promoted to admin through the UI received a full admin session on its first OIDC login and was demoted only on the second, contradicting the documented "on every login the mapped role is applied". - Evaluate eligibility before anything is created or linked. Under Entra group overage an auto-provisioned login was correctly denied, but only after the user row and a successful user.provisioned audit event had been committed. - Record the source IP and user agent on role.changed. It is the most security-relevant event this handler writes and was the one event that could not be correlated during an incident review. - Ask CountActiveAdmins before attempting a demotion that the last-active-admin invariant will refuse. Discovering it inside UpdateWithAudit opened a write transaction and logged a warning on every login for a condition only an operator can change. - Widen the registration budget to 60 per 15 minutes. The limiter keys on client IP, which collapses to a single bucket behind a proxy when server.trusted_proxy_cidrs is unset, so a team rollout of MCP clients could exhaust the previous 20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The setup-token change in ui/host/src/auth.tsx alters the built bundle, and internal/ui/dist is committed and verified by just ui-check. Content hashes change across the shared chunk graph, so the diff is wider than the one source file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Apache-2.0 section 6 withholds trademark rights but says nothing about what use is welcome, so contributors and downstream users have no line to work from. This records it: truthful references, unmodified redistribution, and writing about Fanout need no permission; branding a modified build or a hosted service as Fanout does. The policy is the practical protection a permissive licence leaves available — it is what makes "Hosted Fanout by someone else" actionable while keeping every right Apache-2.0 grants over the code intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Allow telemetry evaluation without AI or SMTP. Login links use purpose-bound 128-bit credentials stored as HMAC hashes and expire after 15 minutes. Closes #168
Keep first-admin credential expiry mutation out of the production API. Closes #169
BREAKING CHANGE: remove ingest.public_endpoint and FANOUT_INGEST_ENDPOINT. Use ingest.advertised_endpoint and FANOUT_INGEST_ADVERTISED_ENDPOINT. The setting only controls collector guidance; it does not expose the OTLP listener. Refs #170
Finish the pre-release configuration cleanup before compatibility aliases become necessary. BREAKING CHANGE: YAML keys and interval environment variables now use ai.* and unit-bearing duration names. Removed names are rejected. Closes #170
Pin patched transitive versions until the MCP SDK resolves them directly. Add bun audit to the shared local and CI gate with no exceptions. Refs #166
Harden the public release path, add reproducible legal notices, withdraw the unverifiable benchmark claim, and define community support boundaries.\n\nRefs #166
Avoid tar SIGPIPE under pipefail when checking release legal files.\n\nRefs #166
Require Authorization: Bearer for OTLP/gRPC and OTLP/HTTP. Rename the browser mutation and delete confirmation headers. BREAKING CHANGE: x-fanout-ingest-token, X-Fanout-Request, and X-Fanout-Confirm-Delete are no longer accepted.
Auto-provision verified OIDC identities as viewers without shared credentials. Keep chat, MCP tools, and owned dashboards available while operator mutations remain protected. Derive the MCP OAuth resource from the canonical public URL, bind transport requests to its Host, and enforce email verification on every login. BREAKING CHANGE: mcp.public_url and FANOUT_MCP_PUBLIC_URL are removed. Set FANOUT_PUBLIC_URL instead.
Public installations need per-user viewer access without a shared credential or external identity provider. Provision only after email-code verification and never before admin setup.
An empty namespace is the product-wide default, but SQL equality treated it as a literal and made default dashboards appear empty.
Replaces the single hand-written documentation page with 33 pages on Starlight in a Diátaxis tree, twelve of them generated from the code they describe and gate-checked against it by `just check`. The site serving fanout.run was one 419-line page whose source was on no live branch — the 2026-08-21 history rewrite dropped `site/` and it was never re-added, so a site edit had no path to production. Generated by `cmd/fanout-docgen`: nine settings pages from `config.Config` struct tags (52 settings), the alert expression variables from `alert.AlertEnv` (11), the role/capability matrix from the middleware's own table, and the application HTTP routes with the credential each requires (45). The route and role pages ask the running authorization logic rather than re-reading it. `internal/api` gains `DescribeRoute`, `RoleCapabilities` and `AllCapabilities` — additive, read-only accessors that expose what `classifyRoute` and `roleCapabilities` already decide. A generator that parsed those instead would be a second authorization model free to drift from the one that runs. That yields guards worth more than the pages: a route registered without a matching case in `classifyRoute` fails the build, as does a route whose group prefix the generator cannot resolve, a configuration group with no authored description, and a capability with no explanation. Each was verified by breaking it deliberately. Pages were written against the source rather than ported. The archived page claimed native OTLP/HTTP was unimplemented, that an AI key was required, that SMTP was required for local sign-in, and documented a `PUBLIC_INGEST` bypass that no longer exists — none carried forward. A review caught two more of the same kind that had crept into the new pages: five telemetry endpoints published as requiring no credential, and a claim that `viewer` cannot run the chat investigator when the middleware grants it. Both are fixed, and both are now generated so they cannot recur. For agents: `llms.txt`, `llms-full.txt`, and a Markdown alternate at `<path>.md` for every page, advertised in the head. The index carries each page's own summary and `read_when` lines and annotates anything not shipped, so a marker arrives before the page does. Every page states its status explicitly rather than relying on the `shipped` default — 29 are `preview`, on evidence: in the week this was written the ingest header was replaced, `FANOUT_MCP_PUBLIC_URL` was removed, namespace scoping inverted, and `viewer` gained `agent:run`. Also here: the README's Scope section names `site/` as a stated exception with its reason; `.github/workflows/site.yml` publishes from main to Cloudflare Pages behind the same generator check; and CI installs the site's dependencies so the gate can build it. Not yet publishing — a `fanout` Pages project and `CLOUDFLARE_API_TOKEN` do not exist yet, and fanout.run keeps serving from its existing container until they do. Follow-ups: #181 (MCP tool generation), #188 (routes registered outside `internal/api`), #189 (a pre-existing flake this surfaced).
…ered (#191) The route reference was built by scanning `internal/api` alone, so it omitted sixteen routes on a page whose own `read_when` invites an operator to audit what an instance exposes. Someone using it to enumerate exposure concluded `/debug/pprof/*` and `/-/metrics` were not served. Missing were the eleven operational, protocol and SPA routes wired up in `cmd/fanout` — `/-/metrics`, six `/debug/pprof/*`, `/mcp`, `/api/mcp`, and the SPA catch-all — and the five `/api/agent` routes in the agent runtime, which the caveat did not even mention because it named only the first group. `POST /api/agent` was missing for a second reason worth stating separately: a group's own root is registered as `group.POST("", ...)`, and the collector required a leading slash, so the one route that actually runs the investigator was dropped silently rather than refused. An empty relative path is now a real route when the receiver is a known group. `Any` registrations are collapsed to one row only when the middleware gives every method the same answer; where it would not, the build fails rather than publish a requirement that is wrong for some verb. Both current `Any` routes qualify. The scan list is a flag with each directory named. A directory in it that registers nothing is an error, so the list going stale is loud rather than a quietly shorter table — the same property the existing per-run check had, moved to per-directory. It counts what a directory registers rather than how much it adds to the total, because a route registered in two places would dedupe to nothing new and read as a directory registering none. The page now says what it covers instead of carrying a caveat, and states which groups are conditional: pprof off by default, MCP on by default, the agent only with a provider key. That distinction is what an exposure audit needs, and the previous text gave none of it. Each guard was verified by breaking it: an unregistered group prefix, a scan directory that registers nothing, and the per-directory count — the last fails the build over a duplicated directory if it counts newness instead. Routes went 45 to 61. Closes #188.
* docs: add a social preview card and a comparison table Two gaps in how the repository meets someone who has just found it. Links to this repository unfurled as a generic grey box on GitHub and in every chat client. `docs/media/social-card.png` is the image to upload as the repository's social preview; it is generated from the `.typ` beside it rather than hand-drawn, so the next edit is a text edit. The header comment carries the render command, including the font path the card needs — Inter is not installed system-wide, and without it the card silently falls back to a substitute face. The README never contained the words Grafana, Loki, Tempo, SigNoz, Jaeger, Prometheus, Datadog, Honeycomb, or ClickHouse, which is what someone types when they are shopping in this space. Each row states the boundary difference rather than a feature score, and the section closes by saying outright that a deployment which outgrows one machine has outgrown Fanout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: gate the social card instead of trusting it Review of the first commit found three ways the card could go wrong quietly. The mark was a fourth hand-copy of the ribbons, sitting outside the guard that internal/brand keeps against ui/host/public/favicon.svg. A ribbon revision would have updated MarkSVG and its testdata under test pressure while this copy kept the old geometry indefinitely. Typst reads the canonical favicon directly now; the render is unchanged. The PNG had no recipe. `just social-card` renders it and does the part a hand-typed command cannot: typst warns about an unknown font family and still exits 0, so the recipe reads the warning back and fails, and renders to a temporary file so a fallback-face card is never left in the tree. The typst version is pinned for the same reason d2 0.7.1 is. The footer was placed out of flow at a fixed offset, so a longer headline would have drawn the divider straight through the body copy with typst reporting nothing. It is pushed down by flexible space instead: the same overflow now spills onto a second page, and a two-page render fails the PNG export outright. Verified by rendering: the card from the canonical favicon is identical to the committed one, the recipe fails against an empty font directory and leaves the committed PNG untouched, and a deliberately overlong headline fails the export rather than overlapping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: drop a pricing claim about other vendors The managed-services row said those products carry per-gigabyte pricing. Their pricing models differ from one another and change without notice, so the claim was both a swipe and a fact this repository cannot keep true. The row now says what is actually different: someone else runs the system, and the telemetry leaves the network to reach it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: correct the card's own instructions The header still gave a bare `typst compile` line, which no longer works: the mark now comes from ui/host/public/favicon.svg, and typst sandboxes an input to its own directory, so the documented command fails on the image unless `--root .` is passed. It points at `just social-card` instead, which passes the root, pins the version, and fails on a missing font — none of which a hand-typed command does. `mktemp -t social-card` also reserved a name and the recipe appended `.png` to it, so typst wrote to a path nothing had reserved and the reserved file was left behind on every run. A temporary directory reserves the whole name. Verified: `just social-card` re-renders the committed PNG byte-identically. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: give the new site the card as its og:image Rebasing onto main brought the documentation site with it, and the site has the same gap the card was made for: Starlight emits `twitter:card: summary_large_image` and never an image to pair with it, so links to fanout.run unfurled as an empty large card. The card moves from docs/media to site/public, where GitHub still takes it as the repository preview and the site can serve it at /social-card.png. The recipe writes there now, and `site-build` runs a check beside check-tables that asserts every built page carries an og:image, that the target is in the build, and that its real PNG dimensions match the hand-typed width and height — CI has no font path to re-render the card with, so that is the part a gate can honestly do. Ran `npm run build` in site: 33 pages, both checks pass, and the built head carries og:image pointing at https://fanout.run/social-card.png. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#193) * docs: generate the MCP tool reference from the server's own tools/list The MCP reference was hand-written, and it was the last page describing a surface a reader acts on with nothing keeping it level with the code. It named four input names. The server exposes eleven. Absent entirely were `trace_id` — the input that makes `trace_detail` fetch a specific trace rather than a guess — `search` and `severity` on `search_logs`, and every input of all four dashboard tools, including which are required. The page also stated that `service_performance` and `trace_detail` "additionally accept `service`", which reads as a closed list and is wrong: `search_logs` accepts it too. `internal/mcp/describe.go` follows internal/api/describe.go. It does not read the registration calls: it starts a real server over an in-memory transport and issues `tools/list`, the same request a connecting agent makes, and publishes the answer. A generator that parsed registerTools would describe the shape of the code; this asks the server the question its clients ask, so a tool renamed, re-annotated, or given a new input cannot be documented as it used to be. Mutation semantics come from the server's annotations rather than from the tool's name. Absent hints default to true per the MCP spec, because reading a missing destructiveHint as false would publish a mutating tool as safe — and "this tool does not modify anything" is a claim someone connects an agent on. The server is built with a non-nil dashboard service deliberately: a nil one registers no dashboard tools at all, so the page would have quietly described five tools instead of nine, two of which mutate. A test pins that. site.yml listed only internal/config as a generator input while claiming to enumerate them. It now lists all of them — internal/alert, internal/api, internal/agent, internal/mcp and cmd/fanout had accumulated behind it. Verified locally: the drift guard fires (a changed tool description fails `--check` naming the page, and passes again on restore); the summary table's anchors resolve in the built HTML; the three internal links exist; llms.txt indexes the page with its summary and both read_when lines; `-race` and `-count=3` are clean. Closes #181. * docs: check the MCP page's claims instead of asserting them Review findings on the generated MCP reference. The page stated "Every tool is closed-world: it reads or writes what this instance holds and reaches nothing else" as flat prose, while ToolDoc.OpenWorld was computed and never read. A tool registered without an openWorldHint defaults to open-world per the spec, and would have been republished under that blanket promise. `--check` could not catch it: a regenerated page carrying the same false claim is self-consistent. The renderer now refuses. Tool titles and descriptions were interpolated into MDX with no escaping. A description containing a pipe would have split its table row into phantom columns — wrong output rather than broken output, so nothing downstream would have noticed; check-tables.mjs asserts that tables are wrapped, not that their rows have the right shape. One containing `<` or `{` would have failed the site build instead. Both are escaped now, along with newlines, which end a row. The escaping is a no-op on every current description, which is why it needed adversarial input to verify rather than a passing build. Also: an empty Title was accepted where an empty Description was refused, and the renderer emits the title as `**%s** — description`, so it would have published a heading beginning with stray emphasis. The scope test filtered by tool name and asserted nothing when the filter matched nothing, so renaming either tool would have switched it off silently; it also compared inputs as a subset, so a field added to QueryInput would have passed while the page's claim that the two share one scope went stale. And the ServerSession was leaked on the client.Connect error path — the same leak the deferred close exists to prevent, on the path that returns before reaching it. The handshake is now bounded, so a transport that never completes fails the build rather than hanging it. Each guard verified by breaking it: dropping OpenWorldHint, blanking a Title, and putting `p50 | p95 | p99 over a <window> with {braces}` in a description — which now renders as one cell of literal text and builds. * docs: fail on a schema or tool name the MCP page cannot render Second review round. Three guards; the generated page is byte-identical. `schemaType` returned `any` for a property whose schema names no type, while `toolInputs`' own comment two functions above says it "refuses anything it does not recognise rather than treating an unreadable schema as 'no inputs'". The comment described what was meant, not what the code did. The SDK emits `$ref`, `anyOf` or a bare `enum` for shapes it cannot reduce to one type — making an input a pointer or an interface is enough — and `any` publishes as a deliberate "accepts anything" rather than "not determined". It is an error now, naming the keys the schema does carry so the message points at the cause. The summary table links each row to the tool's own section, whose heading is the tool name. That holds only while the name is already what a slugger would produce from it: a name with a capital or a dot slugs to something else and every row link on the page goes dead. Nothing downstream catches a dead in-page anchor — `--check` compares the generator's output to itself, check-tables.mjs checks wrappers, and the build succeeds — so the assumption is now checked where it is made. site.yml's check step still described the reference as generated from internal/config, internal/alert and internal/api, contradicting the trigger paths eight lines above that now correctly name internal/agent, internal/mcp and cmd/fanout too. It points at that list rather than restating it, so the next source to be added cannot leave it stale again. Verified by breaking each: a tool renamed to `Observability.Overview` is refused by name, and schemaType has tests for `$ref`, `anyOf`, a bare enum, `["null"]` and an empty type, plus that it still renders unions with null dropped.
* feat(ui): put the web app on the documentation site's palette The app ran on Mantine's stock theme: teal primary, Inter named but never loaded, and no dark mode at all. The documentation site, meanwhile, has a palette with reasoning behind it — Ayu Dark surfaces, a violet accent, IBM Plex split by role — and a comment claiming the app already shared it. It did not. Nothing crossed between them: not a hex, not a typeface, not a variable name. The teal was the substantive problem. `healthColor()` returned "teal" for a healthy service while `primaryColor: "teal"` painted every button and link, so one hue meant both "healthy" and "clickable" on the same screen. The site picked violet precisely to avoid that, violet being the one hue in the palette that carries no status. ui/tokens.ts now holds the palette — the Ayu ramp, the violet accent, the three health hues and a categorical series palette taken from the brand mark — and ui/theme.ts binds it to Mantine under semantic names: brand, ok, warn, bad, info. A component asks for the accent or for a health state and gets whatever the palette holds, so re-hueing the product is a change to one file. The values mirror site/src/styles/fanout.css; the two are still separate sources kept in step by hand. Dark mode ships with it: defaultColorScheme="auto", a header toggle, and the scheme resolved by an inline script in the document head so the first paint lands on the right ground. The typeface is shipped rather than named — eight weights in the host, four inlined into each single-file embedded view, which needed assetsInlineLimit raised or the build would have emitted loose .woff2 files it never copies. Three latent bugs surfaced, all of them invisible while the app was light-only: the sign-in card hardcoded a white background, dashboard metric tiles used bg="gray.0" and rendered white-on-white, and the MCP app bridge passed theme: "light" as a literal, which would have left every embedded view light inside a dark host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(docker): copy every shared module into the browser build stages The two browser stages copied `ui/theme.ts` by name, so `ui/tokens.ts` never reached the container and both `bun run build` invocations failed on an unresolved import. Globbing the directory means the next shared module does not have to remember this file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#195) Patch and minor drift only, across both browser workspaces: Mantine 9.5.2, TanStack Query 5.102.2 and Router 1.170.32, AG-UI client 0.0.58, uuid 14.0.2, Vite 8.2.2, @vitejs/plugin-react 6.1.0, happy-dom 20.11.6 and the React DOM types. The security overrides in each package.json are untouched, so installs, local checks and CI keep resolving the same pinned fixes. The embedded assets and the notices file are regenerated in the same commit, since both are committed outputs that their own checks compare against a fresh build. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#196) `go get -u ./...` plus a tidy: five direct dependencies and the transitive graph behind them. cel-go 0.31.0, modernc.org/sqlite 1.57.0, grpc 1.83.1, protobuf 1.36.12 and the ag-ui community SDK. No major versions and no API changes — the diff is go.mod, go.sum and the regenerated notices. The Go toolchain stays pinned at 1.26.5. Moving it is a separate decision with CGO and DuckDB implications, and 1.27 changes both encoding/json's escaping of invalid UTF-8 (which internal/ingest asserts against) and the module graph the notices file is generated from. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The workflow actions are pinned to commit SHAs rather than tags, which is what makes this worth configuring: a pinned SHA never moves on its own, so without something proposing new ones the pins quietly rot, and resolving them by hand is exactly the motion the pinning exists to prevent. Dependabot resolves each new SHA, names the release it belongs to, and opens one reviewable pull request. Five graphs are covered: the actions, the Go modules, the two browser workspaces (separate on purpose, each with its own overrides block pinning security fixes) and the npm-based documentation site. Minor and patch updates are grouped per ecosystem so a week of drift arrives as one pull request; majors are left ungrouped so they arrive alone, since those are the ones that need someone to read the migration notes. Note that Bun is a version-updates-only ecosystem for Dependabot — it does not receive security updates, so `bun audit` in `just check` stays the gate for those. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 7.0.2. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](microsoft/TypeScript@v5.9.3...v7.0.2) --- updated-dependencies: - dependency-name: typescript dependency-version: 7.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Author
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
dependabot
Bot
deleted the
dependabot/npm_and_yarn/site/typescript-7.0.2
branch
August 24, 2026 20:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps typescript from 5.9.3 to 7.0.2.
Release notes
Sourced from typescript's releases.
Commits
1e4744dMerge branch 'main' into ts7-releasea5a219cmicrosoft/typescript-go#4558ecfe30dUpdate status localization5de25b5Hide executable name in TypeScript statusd7ce74aShow bundled TypeScript version for packaged servers29be66aCorrect TS 7 release version to 7.0.2ed2bd1bMerge branch 'main' into ts7-release8873075Bump the github-actions group across 1 directory with 3 updates (microsoft/ty...9427131Set up stable / nightly extension split, other prep (microsoft/typescript-go#...d4eaca5microsoft/typescript-go#4549Maintainer changes
This version was pushed to npm by microsoft1es, a new releaser for typescript since your current version.
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)