|
| 1 | +--- |
| 2 | +"@objectstack/plugin-dev": patch |
| 3 | +"@objectstack/types": minor |
| 4 | +--- |
| 5 | + |
| 6 | +fix(plugin-dev,types): the production escape hatch stops being silent (#3900) |
| 7 | + |
| 8 | +`DevPlugin.init()` refuses to run under `NODE_ENV=production` (ADR-0115 D6), and |
| 9 | +`OS_ALLOW_DEV_PLUGIN` overrides that refusal. As shipped, the override returned |
| 10 | +early with **no output at all**: the process ran the development assembly while |
| 11 | +every log line and the ready banner read like an ordinary production start. |
| 12 | + |
| 13 | +That reproduces, one level up, the defect the guard exists to close. The guard's |
| 14 | +own precedent says so — `OS_ALLOW_DEGRADED_TENANCY` boots degraded *and brands |
| 15 | +it everywhere an operator looks*, and `OS_ALLOW_DRIVER_CONNECT_FAILURE`'s |
| 16 | +contract is "logged loudly at startup". An escape hatch that says nothing leaves |
| 17 | +the operator's only evidence of a degraded state in an env var they may not have |
| 18 | +set themselves. |
| 19 | + |
| 20 | +**The override now brands itself, twice.** A warning at `init()` — emitted |
| 21 | +before any assembly work, so it survives an assembly step that later throws — |
| 22 | +and a repeat on the ready banner, which is the surface an operator actually |
| 23 | +reads: |
| 24 | + |
| 25 | +``` |
| 26 | +⚠ DEV ASSEMBLY UNDER NODE_ENV=production (OS_ALLOW_DEV_PLUGIN is set) — the boot |
| 27 | + guard was explicitly overridden. This process is running the DEVELOPMENT |
| 28 | + assembly, which is not hardened for production traffic (ADR-0115 D6). |
| 29 | + • Auth secret is the default published inside @objectstack/plugin-dev. It is |
| 30 | + public, so anyone can mint a session this stack accepts. Pass `authSecret` |
| 31 | + explicitly. |
| 32 | + • Data goes to the in-memory driver with persistence disabled — every record |
| 33 | + is lost when this process exits. |
| 34 | +``` |
| 35 | + |
| 36 | +Only hazards that are live for *that* configuration are named: the secret line |
| 37 | +is suppressed when the operator passed their own `authSecret`, and the driver |
| 38 | +line when the `driver` toggle is off. The dev-admin seed is deliberately absent |
| 39 | +— `plugin-auth`'s `maybeSeedDevAdmin` is hard-gated to |
| 40 | +`NODE_ENV === 'development'` and cannot fire on this path, so warning about it |
| 41 | +would spend the attention the real hazards need. |
| 42 | + |
| 43 | +**New export — `resolveAllowDevPlugin()` (`@objectstack/types`).** The flag moves |
| 44 | +off a bare `process.env['OS_ALLOW_DEV_PLUGIN'] === '1'` and joins the |
| 45 | +`OS_ALLOW_*` family's shared truthy vocabulary, next to |
| 46 | +`resolveAllowDegradedTenancy` / `resolveAllowDriverConnectFailure`. |
| 47 | + |
| 48 | +FROM → TO for operators: `OS_ALLOW_DEV_PLUGIN=1` keeps working unchanged. |
| 49 | +`OS_ALLOW_DEV_PLUGIN=true` (and `on` / `yes`, case-insensitive, surrounding |
| 50 | +whitespace ignored) **now takes effect** where the strict comparison previously |
| 51 | +ignored it and failed the boot. That is a widening, in the direction an operator |
| 52 | +setting the flag already intended; falsy and unrecognised values still refuse to |
| 53 | +boot, and unset still means "fail fast". If you were relying on |
| 54 | +`OS_ALLOW_DEV_PLUGIN=true` being inert as a way to keep the guard armed, unset |
| 55 | +the variable instead. |
| 56 | + |
| 57 | +No change to the refusal path, which this issue re-verified end to end: |
| 58 | +`kernel.use()` only registers, `initPluginWithTimeout` does not catch, |
| 59 | +`bootstrap()` rethrows, and `os serve`'s outer handler prints the message and |
| 60 | +exits `1`. The `throw` is genuinely fatal here, so it needs none of the |
| 61 | +`process.exit(1)` the tenancy guard required for sitting inside a broad `catch`. |
0 commit comments