From 83a8c5a6441a4d2e771c8500f633ee4be7236674 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 11:35:56 +0000 Subject: [PATCH 1/2] docs(proposal): GitHub authentication for the hosted tunnel-hub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 6 of the warm-loop hub work: add owner-only GitHub authentication so a hosted hub.mxcli.org shows each user only their own previews and gates who may register a runtime, while self-hosted hubs stay fully open (opt-in via config). Two planes, both keyed to a single GitHub OAuth App: a browser web flow with a .mxcli.org session cookie for SSO across preview subdomains (viewer.login == preview.Owner), and a headless device-flow bootstrap that mints a hub API key bound to the GitHub login for run --hub registration (X-Hub-Key → stamp Owner). Adds Backend.Owner + filtered listing, the HTTP/flag surface, security notes, five implementation slices, and open questions (key persistence, web bootstrap, OAuth App ownership). Repo/team sharing and GitHub App installs are future work. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .../PROPOSAL_hub_authentication.md | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 docs/11-proposals/PROPOSAL_hub_authentication.md diff --git a/docs/11-proposals/PROPOSAL_hub_authentication.md b/docs/11-proposals/PROPOSAL_hub_authentication.md new file mode 100644 index 000000000..66eeeb25f --- /dev/null +++ b/docs/11-proposals/PROPOSAL_hub_authentication.md @@ -0,0 +1,259 @@ +--- +title: mxcli tunnel-hub — GitHub authentication (hosted hub.mxcli.org) +status: proposed +date: 2026-07-26 +--- + +# Proposal: `mxcli tunnel-hub` — GitHub authentication for the hosted hub + +**Status:** Proposed +**Date:** 2026-07-26 +**Author:** Generated with Claude Code + +This proposal adds authentication to the multi-tenant tunnel-hub so a **hosted** +instance (`hub.mxcli.org`) can (a) show each user only their **own** app previews +and (b) limit who may register a runtime. It is the security follow-up flagged in +[`PROPOSAL_mxcli_dev_warm_loop.md`](PROPOSAL_mxcli_dev_warm_loop.md) ("this version +uses one shared `--secret` and open registration … per-tenant auth is a follow-up") +and continues that document's slice numbering as **slice 6**. + +**Self-hosted hubs stay open.** Everything here is opt-in via config; a hub started +without the GitHub flags behaves exactly as it does today. + +## Decisions locked (this proposal) + +- **Access model: owner-only.** A viewer sees a preview iff their GitHub login is the + one that registered it. No repo/team sharing in this cut (revisitable — see + *Future work*). +- **Identity mechanism: a single GitHub OAuth App.** Web flow for browsers, device + flow for the headless CLI/agent. No GitHub App installation, no repo-access checks. + +## Problem + +Today (`cmd/mxcli/tunnelhub/`): + +- **No viewer auth.** Anyone who knows a preview subdomain reaches the app; the admin + overview at `hub./` lists *everyone's* previews. +- **No owner identity.** `Backend` (registry.go) carries `Project/Branch/Prefix/...` + but nothing about *who* owns it. `List`/`/api/backends` cannot filter per user. +- **Registration is a shared secret.** `/api/register` is gated (optionally) by one + hub-wide `RegisterSecret` (`X-Hub-Secret`, from `--hub-secret`). Everyone who can + register shares the same secret; the returned token is opaque and owner-less. + +For a public `hub.mxcli.org` this is unacceptable: previews are world-readable and any +holder of the one secret can register. We need per-user isolation on both planes — +**who may view** a preview and **who may register** one — keyed to a GitHub identity +(every Claude Code web user already has a GitHub account and repo). + +## Goals + +- A viewer of `*.mxcli.org` must authenticate with GitHub and may reach only previews + they own. +- A Claude Code web session (headless) can register a runtime with a **per-user** + credential, not a shared secret. +- Single sign-on across preview subdomains (log in once, not per app). +- The GitHub OAuth token never leaves the trust boundary it belongs to (browser↔GitHub, + or mxcli↔GitHub) — the hub sees only what it mints. +- **Zero behaviour change for self-hosted hubs** when the GitHub flags are absent. + +## Non-goals (this cut) + +- Repo-based or team sharing (owner-only for now). +- GitHub App installation flow / fine-grained repo permissions. +- Persisting the registry to disk (still in-memory; keys are the only new durable state + — see *Open questions*). +- Authenticating the chisel tunnel per-user (still the shared `TunnelAuth`; the + owner-scoped registration token remains the practical tunnel gate — see *Security*). + +## Design overview: two planes, both keyed to GitHub + +``` + ┌───────────── browser ─────────────┐ ┌──────── Claude Code web / CLI ────────┐ + │ GET app-xyz.mxcli.org │ │ mxcli auth hub login (device flow) │ + │ └─ no cookie → 302 GitHub OAuth │ │ └─ GitHub device code → hub mints │ + │ web flow → callback on hub │ │ an API key bound to the login │ + │ → signed cookie .mxcli.org │ │ mxcli run --hub → X-Hub-Key: │ + │ └─ cookie.login == preview.Owner │ │ └─ hub: key → login → stamp Owner │ + │ ? proxy : 403 │ └───────────────────────────────────────┘ + └───────────────────────────────────┘ +``` + +### Plane 1 — Viewer (browser): GitHub OAuth web flow + +New auth middleware wraps the front handler in `server.go` (`ServeHTTP`) for **preview +subdomains and the admin page**, and is skipped for the chisel WS control path and ACME +HTTP-01 (neither is a browser): + +1. Request to `app-xyz.mxcli.org` with no valid session cookie → `302` to GitHub's + `authorize` endpoint (`client_id`, `redirect_uri=https://hub.mxcli.org/auth/github/callback`, + `scope=read:user`, signed `state` carrying the return URL). +2. GitHub redirects back to `hub.mxcli.org/auth/github/callback`; the hub exchanges the + code for a GitHub token **server-side**, calls `GET /user` once to learn the login, + then sets a **signed session cookie** and 302s back to the original preview URL. +3. Cookie is `Domain=.mxcli.org; Secure; HttpOnly; SameSite=Lax` so it is **single + sign-on across every `*.mxcli.org` subdomain**. Payload: `{login, exp}`, signed + (HMAC) with a hub secret; no GitHub token stored in the cookie or server-side. +4. On each preview request the middleware verifies the cookie and checks + `cookie.login == backend.Owner`; mismatch → `403` (a small "not your preview" page); + match → existing reverse-proxy path. + +The callback lives on `hub.mxcli.org` (one registered OAuth callback URL) even though +the protected resource is a subdomain — the cross-subdomain cookie makes that work. + +### Plane 2 — Registration (headless CLI/agent): hub-issued API key + +GitHub does not hand out generic service API keys — it issues *identity* (OAuth tokens, +PATs). So the hub **mints its own key**, seeded once by a GitHub login, and +`run --hub` sends that key — never a GitHub token — on `/api/register`. + +**Bootstrap (`mxcli auth hub login`), once per environment:** + +1. mxcli starts GitHub's **device flow** (`POST /login/device/code`), prints + `Go to https://github.com/login/device and enter ABCD-1234`. +2. User authorizes in any browser; mxcli polls `POST /login/oauth/access_token` until it + gets a GitHub token. **This token stays local to mxcli.** +3. mxcli calls the hub `POST /api/keys` with `Authorization: Bearer `. The + hub validates it against `GET https://api.github.com/user`, learns the login, mints an + opaque **hub API key** bound to that login, stores `key → login`, and returns the key. +4. mxcli caches the key in `~/.mxcli/auth.json` (the same file the Mendix marketplace PAT + already uses, mode `0600`), keyed by hub host. + + *Trade-off:* step 3 briefly sends the GitHub token to the hub. Alternative that keeps + the token entirely off the hub: run the whole OAuth on the hub (`auth hub login` opens + a hub URL that does GitHub web-flow and displays a key to paste back). Slightly more + friction; called out in *Open questions*. + +**Register:** `run --hub` reads the cached key and sends `X-Hub-Key: ` on +`/api/register`. The hub resolves `key → login`, stamps `Backend.Owner = login`, and +returns the existing registration token. Heartbeat/deregister continue to use that +per-registration bearer token unchanged. + +**Claude Code web:** the key must survive container reaping. Two supported paths: +- A **repo/environment secret** `MXCLI_HUB_KEY` the user sets once (read by `run --hub` + before falling back to `~/.mxcli/auth.json`) — deterministic, best for web sessions. +- `mxcli auth hub login` on demand (device flow) — fine for a local dev machine. + +## Data model changes + +`Backend` (registry.go) gains one field: + +```go +Owner string `json:"owner"` // GitHub login that registered it ("" = anonymous/self-hosted) +``` + +- `RegisterRequest` is unchanged on the wire; `Owner` is derived server-side from the + `X-Hub-Key` → login lookup, never trusted from the client body. +- `identity()` gains `Owner` as its first component so two users' identically-named + projects/branches never collide on one slot. +- `List(sortKey, viewerLogin string)` filters to `b.Owner == viewerLogin` when auth is + on; `viewerLogin == ""` (self-hosted / auth off) returns all, preserving today's + behaviour. `/api/backends` and the admin page pass the cookie login through. + +New durable state: a `keys` store `map[string]string` (hub key → GitHub login). In-memory +for the first cut (see *Open questions* re: persistence). + +## HTTP surface + +| Method & path (on `hub.mxcli.org`) | Purpose | +|---|---| +| `GET /auth/github/login` | Begin web flow (302 to GitHub), signed `state` = return URL | +| `GET /auth/github/callback` | Exchange code, set `.mxcli.org` session cookie, 302 back | +| `POST /auth/logout` | Clear the session cookie | +| `POST /api/keys` | Mint a hub API key (auth: `Bearer `) — bootstrap | +| `DELETE /api/keys` | Revoke the caller's key | +| `POST /api/register` | **now** authed by `X-Hub-Key` (was `X-Hub-Secret`) → stamps `Owner` | +| `GET /api/backends`, admin `/` | filtered to the viewer's `Owner` | + +Preview subdomains: unchanged path, now behind the viewer-auth middleware. + +## Config / flags (`mxcli tunnel-hub`) + +``` +--github-oauth-client-id GitHub OAuth App client id (enables viewer + key auth) +--github-oauth-client-secret GitHub OAuth App client secret (env: MXCLI_HUB_GH_SECRET) +--session-secret HMAC key for the session cookie (env: MXCLI_HUB_SESSION_SECRET) +--require-auth require a valid session for every preview + register (default: on + when a client id is set; forced off when it is not) +``` + +**Absent client id ⇒ open mode** — the middleware is a no-op, `/api/register` keeps +honouring the legacy `--hub-secret`, `List` returns everything. A self-hosted +`mxcli tunnel-hub --domain example.com` is byte-for-byte today's behaviour. + +Client (`mxcli run --hub` / `mxcli auth`): +- `mxcli auth hub login [--hub https://hub.mxcli.org]` — device-flow bootstrap. +- `mxcli auth hub status` / `logout`. +- `run --hub` sends `X-Hub-Key` from `MXCLI_HUB_KEY` or `~/.mxcli/auth.json`; falls back + to `--hub-secret` (`X-Hub-Secret`) for open self-hosted hubs. + +## Security considerations + +- **Cookie scope.** `Domain=.mxcli.org` is deliberate (SSO), so the signing key must be + strong and rotatable; cookie carries only `{login, exp}`, HMAC-signed, short TTL with + silent re-auth (the GitHub session makes the redirect invisible when still valid). +- **GitHub token containment.** Never stored in the cookie; only used transiently + (browser callback exchange, or device-flow key mint) and discarded. `scope=read:user` + only. +- **Tunnel plane.** The chisel control connection still uses the shared `TunnelAuth`; a + cross-user request can't reach another app because routing is by owner-checked + subdomain and the reverse port is server-allocated, not client-chosen. Per-user tunnel + auth is deferred (non-goal) — the owner check on the front is the enforced boundary. +- **Key theft.** A leaked `X-Hub-Key` lets an attacker register previews *as that user* + (annoyance/quota), not view others' apps. `DELETE /api/keys` + short-lived keys + mitigate; rotating is cheap (re-run `auth hub login`). +- **Open-mode safety.** Because auth is gated on `--github-oauth-client-id`, a + misconfigured hosted hub that forgets the secret fails **closed** only if + `--require-auth` defaults on with a client id present; document that a client id + without a session secret refuses to start. + +## Implementation slices + +1. **Owner field + filtered list** (no auth yet): add `Backend.Owner`, + `List(sort, viewer)`, thread a viewer through admin/`/api/backends`. Pure refactor, + `""` viewer = today. Tests: registry filtering. +2. **GitHub OAuth web flow + session cookie**: `/auth/github/*`, signing, middleware on + preview + admin; skip WS/ACME. Tests: middleware allow/deny, cookie round-trip + (httptest, GitHub stubbed). +3. **Hub API keys + registration by key**: `POST/DELETE /api/keys`, `X-Hub-Key` on + `/api/register` → stamp `Owner`; keep `X-Hub-Secret` for open mode. Tests: mint/resolve/ + revoke, register stamps owner, open-mode fallback. +4. **Client**: `mxcli auth hub login/status/logout` (device flow), `run --hub` sends + `X-Hub-Key` (env → auth.json → legacy secret). Tests: auth.json round-trip, header + selection. +5. **Wire-up + docs + E2E** against `hub.mxcli.org`: flags in `tunnel-hub`, `run-local` + skill + docs-site, CLAUDE.md status line; verify owner isolation end-to-end (two + GitHub users, cross-access = 403). + +## Testing + +- Unit: registry owner-filtering; cookie sign/verify; middleware allow/deny/redirect; + key mint/resolve/revoke; header precedence in the client. +- Integration (`-tags integration`, GitHub stubbed via httptest): full web-flow redirect + chain; register-with-key stamps owner; a second user's cookie gets 403 on the first's + preview. +- E2E (manual, documented): real GitHub OAuth App against `hub.mxcli.org`, two accounts, + confirm SSO across subdomains and owner isolation; self-hosted hub with no flags still + fully open. + +## Open questions + +1. **GitHub-token-to-hub during key mint** — accept the brief send (simpler) or run the + full OAuth on the hub so the token never touches mxcli (more friction)? Proposal + assumes the former; easy to switch. +2. **Key persistence** — in-memory keys are lost on hub restart (users re-run + `auth hub login`). Add a small on-disk store (`keys.json`, mode `0600`) in slice 3, or + defer? Registry backends are ephemeral anyway; keys are the only state worth keeping. +3. **Claude Code web bootstrap** — is a repo/environment secret `MXCLI_HUB_KEY` the + expected UX, or should `mxcli init` help provision it? (Affects the web onboarding + prompt in `docs-site/src/tools/bootstrap-prompt.md`.) +4. **`mxcli.org` OAuth App ownership** — who registers/owns the OAuth App and holds the + client secret + session secret for the hosted instance? + +## Future work + +- **Repo-based / team sharing** (the deferred access model): tie a preview to its GitHub + repo and grant view to anyone with repo access — needs a **GitHub App** (installations, + repo-access checks) rather than the OAuth App, and repo identity at registration. +- Per-user chisel tunnel auth (`--authfile` per login) if the shared `TunnelAuth` proves + insufficient. +- Quotas per owner (max concurrent previews) to bound misuse on the hosted hub. From f0a2077860de4126d8d2cdb91a1d5c238acf2ef8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 16:23:56 +0000 Subject: [PATCH 2/2] Fix nightly on Mendix 10.24: gate the 11.x-only building block in 15c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nightly integration matrix (Mendix 10.24 / 11.6 / 11.12) failed only on 10.24: TestMxCheck_DoctypeScripts/15c-fragment-bindings-examples → "failed to build page: building block not found: Atlas_Web_Content.List_Cards" on both engines. 11.6/11.12 and the single-version push-test are green, so it surfaced only in the cross-version nightly. Atlas_Web_Content.List_Cards ships with the Atlas UI in Mendix 11.x but is absent from the 10.x Atlas; the example's building-block rebind demo had no version gate (its comment even wrongly claimed the block is "present in every standard Mendix app"), so on a 10.24 project the whole file ran and mxbuild couldn't resolve the block. (12-styling and 31 already gate their Atlas/design content, which is why only 15c broke.) Add "-- @version: 11.0+" before the P002_Rebound_Block create page (the file's last statement) so filterByVersion skips only that demo on 10.x; the fragment-binding statements above stay ungated and keep 10.24 coverage. Correct the misleading comment. Verified: 15c passes on 10.24 (section skipped, 0 errors) and still runs and passes the building-block section on 11.6.3 (0 errors). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4 --- .claude/skills/fix-issue.md | 1 + .../doctype-tests/15c-fragment-bindings-examples.mdl | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 9ec8e7f11..90cf564b3 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -174,6 +174,7 @@ to the symptom table below, so the next similar issue costs fewer reads. | `retrieve … where [Seq = $Game/MoveSeq + 1]` fails with a bare `mismatched input '+' expecting ']'` — no hint that Mendix XPath can't compute values (this is a Mendix limitation, not an mxcli bug) | Mendix XPath constraints take a literal/token/variable/path on the value side, never an arithmetic expression; the parse error named the token but not the cause | `mdl/visitor/visitor.go` (`enhanceErrorMessage`, `looksLikeXPathArithmetic`/`xpathArithmeticRe`) | Do NOT add grammar support (mxbuild would still reject the XPath). Add an error hint keyed on `mismatched input '<+|*|div|mod>' expecting ']'` (`expecting ']'` only occurs inside a `[…]` constraint) explaining the limitation and the workaround: compute into a variable first, then compare. Also documented in `xpath-constraints.md`. Bug-test `mdl-examples/bug-tests/f8-xpath-arithmetic.fail.mdl`. Findings #8 | | Design properties are written free-form: a `ColorPicker`/`ToggleButtonGroup` value serializes as a plain option (wrong `$Type` for Studio Pro's Appearance tab), and a typo'd key/value (they're case-sensitive) passes `mxcli check`. Also `show design properties ` reports "No design properties found for widget type container" for a valid widget | Root bug: `resolveDesignPropsKey` upper-cased the MDL keyword but the lookup map is **lowercase-keyed**, so `container`→`DivContainer` never resolved — leaving `resolveDesignPropertyValueType` dead code and the theme registry unused on the write/validate paths | `mdl/executor/theme_reader.go` (`resolveDesignPropsKey` case fix) + `mdl/executor/cmd_pages_builder_v3.go` (`astDesignPropToValue` takes theme props) + `mdl/executor/validate_design_properties.go` (new, MDL-WIDGET11/12) wired from `cmd/mxcli/cmd_check.go` + `cmd/mxcli/lsp_diagnostics.go` (cached `themeRegistry`) | Fix `resolveDesignPropsKey` to lower-case the lookup. On write, resolve each flat value's type from the registry **by matching the value against the property's declared options** (see the CE6084 correction below — the control type alone does NOT decide it). On check (`-p` only, when themesource defines properties), walk page/snippet/alter-page widget trees and warn: **MDL-WIDGET11** unknown key (case-sensitivity hint / valid-key list), **MDL-WIDGET12** invalid value (lists allowed values). Warnings, not errors — a newer theme may add keys/values (forward-compat, per `page-styling-support.md:402`). Skip compound (registry doesn't model sub-props) and widgets with no type-specific metadata (pluggable). Bug-test `mdl-examples/bug-tests/typed-design-properties.mdl` | | Follow-up regression from the row above: after typed design properties merged, `mx check` fails **CE6084** "Expected design property _Flex container_ / _Column gap_ / _Align items Y_ … to be of type **Toggle button group**, but found **Custom**" on any page using a flat `ToggleButtonGroup` value (Atlas flex/spacing/typography, e.g. `'Column gap': 'Medium'`). Broke `TestMxCheck_DoctypeScripts` on `12-styling`, `15c-fragment-bindings`, `31-pluggable-datagrid-gallery-v010` (both engines) — green on unit tests, red only in `make test-integration` | `resolveDesignPropertyValueType` mapped `ToggleButtonGroup`→`custom` by control type. But a ToggleButtonGroup selection picks one of a **fixed option set**, so Studio Pro stores it as an **Option** — a `Custom` value type mismatches the declaration. Only a ColorPicker's **off-list** value (a free-form hex) is genuinely Custom. The value type is decided by the **value**, not the control | `mdl/executor/cmd_pages_builder_v3.go` (`resolveDesignPropertyValueType`, now takes the value and reuses `themeOptionAllowed`) | Make it value-aware: value ∈ declared options → `option` (Dropdown, ToggleButtonGroup, predefined ColorPicker swatch alike); off-list **and** `ColorPicker` → `custom`; else `option`; no metadata → `option`. Verified: the three doctype examples pass `mx check` = 0 errors on both engines. Test `TestAstDesignPropToValue_Typed` extended with the `Column gap: Medium` + ColorPicker swatch/hex cases. **Diagnosis pattern**: a value-type/BSON-`$Type` mapping keyed on a *declared control type* is a trap — verify it against `mx check`, never assert it from the type name alone (this is exactly how the original bug slipped in). **Process lesson**: this shipped red because `make test-integration` (mx-check doctype roundtrips) was not run before merge — run it, not just unit tests, for any page/widget-serialization change | +| The **nightly** matrix (Mendix 10.24 / 11.6 / 11.12) fails only on **10.24**: `TestMxCheck_DoctypeScripts/15c-fragment-bindings-examples` → `Execution error: failed to build page: building block not found: Atlas_Web_Content.List_Cards` (both engines). 11.6/11.12 pass; unit tests + push-test (single-version) pass. Looks like a "design property on 10.24" issue but isn't | `15c` demonstrates `use building block Atlas_Web_Content.List_Cards`, an Atlas UI building block that ships in **11.x but is absent from the 10.x Atlas** (the example comment wrongly said "present in every standard Mendix app"). The example had **no `-- @version:` gate**, so on a 10.24 project the whole file ran and mxbuild couldn't resolve the block. (12-styling gates its design-property section at line 186; 31 gates the whole file at line 1 — both already skip on 10.24) | `mdl-examples/doctype-tests/15c-fragment-bindings-examples.mdl` | Add `-- @version: 11.0+` immediately before the `create page … P002_Rebound_Block` block (its last statement) so `filterByVersion` skips only the building-block demo on 10.x; the fragment-binding statements above stay ungated and keep 10.24 coverage. Verified: 15c passes on **10.24** (10 lines skipped, 0 errors) and still runs+passes the section on **11.6.3** (0 errors). **Diagnosis pattern**: a nightly-only, version-specific doctype failure = an example using a construct (building block, widget, syntax) that doesn't exist in the oldest matrix version and lacks a `-- @version:` gate; reproduce locally with `MX_BINARY=~/.mxcli/mxbuild//modeler/mx go test -tags integration -run TestMxCheck_DoctypeScripts/` | | `mxcli run --local`: when a page action throws, the browser shows the generic Mendix error dialog and there is nothing to correlate it against — the runtime's own stdout/stderr (server stack trace, microflow `LOG` output) is swallowed, so a server-side bug can't be told apart from a client one | The runtime JVM was spawned with `cmd.Stdout=log; cmd.Stderr=log` where `log` is an in-memory `syncBuffer` surfaced only on a *startup* failure; during normal operation it goes nowhere on disk | `cmd/mxcli/docker/localboot.go` (`spawnAndConfigure`, `openRuntimeLog`, `LocalRuntime.logFile`, `LocalRuntimeOptions.RuntimeLogPath`) + `cmd/mxcli/docker/runlocal.go` (default `/.mxcli/runtime.log`) + `cmd/mxcli/cmd_run.go` (`--runtime-log`) | Tee the JVM's stdout+stderr to `/.mxcli/runtime.log` via `io.MultiWriter(log, file)` (the in-memory buffer still backs startup-error reporting). Append across restarts with a `=== runtime start … ===` marker; close the handle on Stop/reopen. Default on; `--runtime-log ` relocates, `-` disables. Print the path at boot. Test `TestOpenRuntimeLog`. Findings #25 | | Follow-up to the above (#25 re-test): `run --local` writes `runtime.log` but it stays **nearly empty** — the JVM tee captures startup/JVM output only; **application** logs (microflow `LOG`, server-side exception stack traces) never reach stdout, so a page-action error still can't be diagnosed | A standalone runtime (launched via `runtimelauncher.jar`) attaches **no log subscriber** by default — unlike a Studio Pro / m2ee run, which calls `create_log_subscriber` **after** start. Mendix application logs flow to log *subscribers*, not stdout, so with none attached they go nowhere | `cmd/mxcli/docker/runtime_controller.go` (`RuntimeController.LogSubscriberFile`/`Stdout`, `attachFileLogSubscriber`, called at the end of `Start`) + `cmd/mxcli/docker/localboot.go` (`StartLocalRuntime` sets `ctrl.LogSubscriberFile` to the abs runtime-log path) | After a successful `start` (and on every restart's `Start`, since each fresh JVM has no subscriber), call the `create_log_subscriber` admin action with `{type:"file", name:"mxcli-run-local", autosubscribe:"INFO", filename:, max_size:1GiB, max_rotate:0}`. **`max_rotate:0` is load-bearing**: the JVM stdout tee holds an fd on the same file, and a rotate-rename would detach it. Best-effort (a logging failure must not fail an up runtime — warn to Stdout instead). Pass an **absolute** path (the runtime's cwd is `/runtime`, not mxcli's). Tests `TestStart_AttachesLogSubscriber`, `TestStart_NoLogSubscriberWhenUnset`, `TestStart_LogSubscriberFailureNonFatal`. Findings #25 (round 2) | | Follow-up to round 2 (#25 re-test): the subscriber is registered but `runtime.log` **still** holds only the 4 JVM-banner lines — a probe microflow's `log info/warning/error` and a forced runtime exception produce **zero** lines. Isolation test: boot+subscriber → 0 probe lines; then call `start_logging` → 6 lines | A standalone runtime boots with logging **not started**, so a registered subscriber sits **inert** — nothing is delivered until the logging subsystem is activated. `create_log_subscriber` alone is necessary but not sufficient | `cmd/mxcli/docker/runtime_controller.go` (`configureRuntimeLogging` — renamed from `attachFileLogSubscriber`; now also calls `start_logging`) | After `create_log_subscriber`, call `CallM2EE(c.opts, "start_logging", nil)` in the same step (order: create subscriber → start_logging). Treat an "already started" response as success (`start` re-runs on the DB-update retry / restart paths on a still-running JVM). Still best-effort (warn to Stdout, never fail an up runtime). Test `TestStart_StartLoggingAlreadyStartedIsSuccess`; `TestStart_AttachesLogSubscriber` now asserts the `[start, create_log_subscriber, start_logging]` sequence. **Diagnosis pattern**: when a registered sink receives nothing, check whether the subsystem that feeds it is even *running* — registration ≠ activation. Findings #25 (round 3) | diff --git a/mdl-examples/doctype-tests/15c-fragment-bindings-examples.mdl b/mdl-examples/doctype-tests/15c-fragment-bindings-examples.mdl index 830b669b7..06195082b 100644 --- a/mdl-examples/doctype-tests/15c-fragment-bindings-examples.mdl +++ b/mdl-examples/doctype-tests/15c-fragment-bindings-examples.mdl @@ -84,7 +84,10 @@ describe page BindTest.P001_Bound_Panel; -- action → the first button widget (Atlas placeholder buttons ship with -- no action, so the target is matched by widget type) -- --- Requires the Atlas_Web_Content module (present in every standard Mendix app). +-- Requires the Atlas_Web_Content.List_Cards building block, which ships with the +-- Atlas UI in Mendix 11.x (it is absent from the 10.x Atlas), so this section is +-- gated to 11.0+; the fragment-binding examples above run on every version. +-- @version: 11.0+ create page BindTest.P002_Rebound_Block ( title: 'Rebound Block',