v2.3.0
Surfaces the known scope tree through a sanctioned interface so external consumers (the upcoming luci-app-uapi LuCI frontend, fleet inventory tools, anything that wants to render a scope picker) can enumerate valid scopes without parsing src/lib/scope.uc or hardcoding a copy. Closes openwrt-iac/uapi#5. Also plumbs per-token rate / burst overrides through the mint surfaces, closing a "planned for v2.x" gap that has been carried in docs/tokens.md since 2.0.
Commit-confirmed apply (the ?confirm / /confirm surface) was present in the 2.3.0-rc1 pre-release and has since been deferred; it is not part of the 2.3.0 stable surface. The confirm wire contract is intentionally not frozen into v2 until its authz model is settled and a consumer needs it, so it does not lock in a contract that could only be changed with a major bump. See docs/commit-confirm.md and docs/roadmap.md.
Added
-
New CLI subcommand
uapi-token scopesprinting one scope path per line (sorted, greppable). Pair with--jsonfor a JSON array suitable for piping intojqor any other consumer. The CLI is the durable cross-package interface; it works from any shell, Ansible playbook, or fleet inventory tool that cansshto the router. -
New
scope.known_paths()module export returning a sorted array of scope paths. ucode consumers on the same box (the LuCI frontend in particular)require('scope')and call this directly, matching howuapi-tokenitself imports the module.
Both surfaces enumerate the same internal KNOWN_PATHS const; the accessor (rather than a direct const export) lets the underlying representation change without breaking consumers.
POST /tokensaccepts optionalrateandburstinteger fields;uapi-token createaccepts--rate <N>and--burst <N>. Both writeoption rate '<N>'/option burst '<N>'on the token's uci section. The request-path rate limiter (src/lib/ratelimit.uc'seffective_limits()) has been reading these uci options since 2.0.0; only the mint side was missing.uapi-token show <name>also now surfaces the configured rate/burst when set.
The OpenAPI vendor-extension option from the issue thread is deferred until a code-generated client surfaces with a concrete need; premature spec annotations without consumer evidence is what 2.2.2 / 2.2.3 corrected.
Fixed
-
PATCH no longer deletes uci options the resource does not model. Previously every write verb shared one
diff_applythat deleted any existing uci option not re-emitted bytoUci, so a partial PATCH (e.g. changingunboundverbosity, or any field onfirewall.rules) silently wiped hand-set or stock options the curated model omits (dns64_prefix,icmp_type,synflood_protect, certkey_type/ec_curve,ttylogin, and many more). PATCH now preserves them (RFC 7396 merge-patch: only options inside the resource's own modeled footprint are touched). PUT keeps full-replace semantics: it normalizes the section to the modeled set, so unmodeled options are intentionally dropped on a PUT. An audit of all curated resources against their stock OpenWrt configs found this affected 13 resources. -
A JSON Patch (RFC 6902) that does not touch a write-only secret no longer drops it. The JSON Patch post-image is built from the masked read view (which exposes
has_key, notkey), so a patch that left the secret alone previously produced a post-image without it and tripped conditional-required validation ("key is required when encryption is ..."). The write path now carries forward anywriteOnlyfield the patch did not set (key,preshared_key,private_key,tls_auth,pkcs12), matching what the merge-patch path already did; a patch that explicitly sets a new secret is left intact. -
Three validation rules relaxed to match what stock OpenWrt ships (found by the same audit):
wireless.devicesaccepts an emptycountry(read back as null) and the"00"world regulatory domain (stock 6 GHz default);wireless.interfacesacceptsencryption='owe'(Opportunistic Wireless Encryption, keyless, the stock 6 GHz default) without demanding a key;network.devicesno longer requirestype, so aconfig deviceoptions-override section (name + macaddr, no type, asconfig_generateemits on some targets) round-trips.
Internal
- New integration test
tests/integration/44_stock_config_test.shround-trips every curated CRUD resource and singleton whose package ships in the bare OpenWrt 25.12.4 image (firewall, network, dhcp, dropbear, system): GET the section, adopt if unmanaged, PUT-self (or PATCH-self for singletons), then GET again and assert the persistable shape did not drift. A 422 or a diff surfaces a regression where uapi rejects (or silently mutates) what the platform itself ships. Resources from optional packages (snmpd, lldpd, vnstat, mwan3, etc.) are deferred to a follow-up that wires their install at VM-setup time. First run forced four schema relaxations to bring API rules in line with the platform:igmpaccepted byfirewall.rules/firewall.redirects;*/anywildcards accepted byfirewall.ruleszone refs;dhcp.serversno longer requires the referenced network interface to exist (stock shipsdhcp.wanagainst an absentnetwork.wanon x86);is_valid_cidr_anyaccepts IPv6 CIDR (used bymwan3.rulesonce mwan3 coverage lands).