Adversarial audit of prplOS MR !3019 — Ambiorix v11.44.1 upstep + QoS forward-port

MR: prpl-foundation/prplos/prplos!3019, "FEAT-664/PCF-2755 ambiorix v11 upstep", sah-next_v11latest-24.10, opened 2026-07-28, state opened at time of audit.

This report is addressed to the prplOS reviewers of !3019 and to the SoftAtHome contributors who authored the change. Its method is deliberately adversarial: every imported commit was read on the assumption that something unwanted could ride along, and every claim below is tied to a number, a file, or a commit URL. Where the evidence does not support a strong statement, the report says so.

Scope & method

MR !3019 repoints two prplOS feed pins in profiles/: feed_amx.yml main@96c1a3b220cee49damx_v11@623ddb685006992a (23 Ambiorix components), and feed_prplos.yml main@5219cc0498e1ba50dev_forward_port_qos_optimizations_main@7eba715c3a1a84f1 (4 QoS components).

Across those 27 components the two pins resolve to 1,013 non-merge commits and 0 merge commits (manifest.jsonl, manifest-summary.tsv), computed as git rev-list --no-merges <old_sha>..<new_sha> with set semantics rather than as a linear range: 20 of 27 components are non-fast-forward relative to their old release tag (inputs/ranges-notes.md §5). For divergence from the real default branches, see Default-branch coverage of the pinned branch, which compares the proposed pins with current fetched default-branch history.

Two baselines are used (PLAN.md decision 2). Baseline A is the current feed pin (old_sha), what prplOS ships today, and the floor of the rated commit universe. Baseline B is the public SoftAtHome upstream tag (inputs/baseline-b.tsv), used only to mark which commits are prpl_only; the four QoS components have no public upstream, so prpl_only is not computed for them (inputs/upstream-notes.md).

Two evidence streams feed this report. Deterministic checks (checks/, C1–C10 plus C10b) are read-only git and GitLab-API scripts covering patch-id re-authoring, DCO presence, static test/CI metrics, pin consistency, feed anomalies, description integrity, CI template pinning, prpl-only provenance, commit-context loss and default-branch coverage. Per-commit rating covered all 1,013 commits via a pre-classifier, two independently framed LLM raters and an Opus 5 adjudication pass (see Rater method & limitations).

Scope limit, agreed up front (PLAN.md decision 5): nothing was built, run, or measured: no component test suite, no prplOS cram run, and no dynamic memory data (valgrind, massif, RSS). All test and CI signal is static.

Headline findings

  1. acl-manager ships a script that deletes persistent ACL configuration on a transient read error, and the init script still calls it. 6daa7e419901 adds scripts/access_check.sh, which runs rm -rf "$DIR" on any /cfg/etc/acl/*/ directory whose *.json cat fails or that lacks +x; 5604dad367c2 installs it to /usr/bin and calls it from acl-manager.sh. At the new pin the script is byte-identical and acl-manager.sh still runs it from boot) and start), but the makefile install line is gone, dropped incidentally by the pin commit itself (a BAF-autogenerated procd refactor, not a fix), leaving the destructive path one restored makefile line from firing. Neither commit is signed off; both are titled "Issue: NET-7541 missing parameter in the inform message".
  2. mod-amxb-ubus carries three verified defects at the new pin (94253172aa4a): amxb_ubus_msg_new() sets msg->recv_msg = helper then deletes helper, leaving recv_msg dangling (f71cfd1a170c); an oversized non-HTABLE reply is logged and dropped instead of sent, breaking the three-message protocol (fa68fac4cc11); and a use-after-free fix dereferences amxb_ubus_ctx->dm above its own guard (a9580943f0b8).
  3. The MR's only heap-memory-optimization commit is one of the 22 BLOCKERs. libamxd c860ee03fe93 changes param->flags and amxd_param_build_description() from amxc_var_t* to amxc_set_t* in public headers, makes amxd_param_copy() store source->name unowned behind shared_name=1, and deletes 10 destroy assertions from test_amxd_param_delete. All three still hold at pin ce4148d5ffe2.
  4. libamxj can hang, and drops shipped public symbols. c69c3e9f6655 loops in amxj_read_json() ignoring amxj_read()'s return while amxj_read() exits on read() < 0 without touching parse_state: a directory path spins forever, untested and unfixed in range. fefa24f5ca2b removes amxj_convert(), amxj_writer_set_config() and amxc_var_push_jstring_t() from amxj_variant.h under the subject "return written length for amxj_write".
  5. Twenty of the 22 BLOCKERs are still present in the tree this MR would ship. Two were repaired later in the same range: libamxb 47d6b909735b (fixed by 1337f81fb464) and libamxp fa1fa51ad42c, which deleted bool verify from the fully public struct _amxp_expr under a v2.12.1 → v2.13.0 bump before a694f57ea76c restored it ahead of the sah-v2.13.0 tag; so no release shipped that break. Among the 20 that do ship: amxo-cg 5daee2209ae6 renames -Gjson to -Gjson-dm while gen_json_write_dm() still passes the old "json" key, so -Gjson-dm,<file> dumps to stdout; and amx-cli d1f6a2f09a68 still leaves test_cli_can_set_output_format_json unregistered.
  6. Six malformed-ODL parser tests were deleted and never restored. libamxo 3828c4edd099 removes test_parse_list_parse_errors() and its six assertions; they are still absent at pin ede9e06f2f03. The body itself states that the new heap-owned strings leak on syntax errors and that no %destructor exists.
  7. Nothing in this MR is hash-pinned. All 27 changed packages switch PKG_SOURCE_URL from a tagged tarball to a .git clone with PKG_SOURCE_VERSION set to a branch name, and all 27 add PKG_MIRROR_HASH:=skip where none had it before (checks/c5-feed.md). Every build fetches whatever the branch resolves to at fetch time, not the 1,013 commits audited here. Separately, 26 of 27 components include a shared CI template from a floating main with no version pin (25 of 27 at the old sha; checks/c7-ci-template.md).
  8. Attribution was stripped at scale on re-import. checks/c1-reauthored.tsv records 206 patch-id twin pairs across 23 of 27 components: the same patch re-committed under a different identity. 168 changed author, 153 lost a Signed-off-by, and 33 have their new-side body reduced to a bare GitOrigin-RevId: trailer. Across the import, 808 of 1,013 commits carry no Signed-off-by trailer, 802 of them from one account, sahbot.
  9. Commit context was lost on re-import. 102 twin pairs have default-side prpl Jira keys absent from the pinned-side twin; one is a reverse-direction backport.[^c9-backport] Six pairs replace all prpl references with NET references, and 21 lose their non-trailer descriptions (C9; categories overlap).
  10. The MR description does not describe the MR. Its top table cites two hashes that resolve nowhere (amx_v11@f4904420, dev_forward_port_qos_optimizations_main@81364654; the actual pins are 623ddb685006… and 7eba715c3a1a…). It says "Three files, two commits" where the diff is 12 files, 4 commits, and "Every MR below must merge before this one" while all 6 dependency MRs are still opened (checks/c4-pins.tsv).
  11. Seven TR-181 QoS classification parameters disappear from the cram fixture, undisclosed. .gitlab/tests/cram/fixtures/obuspa.expected loses 7 lines and gains none, all Device.QoS.Classification.{i}.*, and those exact seven trace to four tr181-qos ODL commits (17ed9e44376e, 2a8103485299, c0243336c52b, c5b5fa485a59) that drop %persistent parameters as a stated RAM optimisation. The description mentions only the ForwardingPolicy encoding change.
  12. 171 default-branch commits have no exact patch-ID twin in the pin: 64 are present verbatim, 36 have a near twin, and 71 remain unresolved by automated checks. Hand review identifies 2 confirmed missing main-side code fixes in amx-cli and amxo-cg, plus libsahtrace release reverts the pin never received, while 10 of the 16 unresolved code rows are present in modified form.

The 22 BLOCKER commits

A BLOCKER is a commit the adjudicator judged must not merge as-is: a verified defect, a removal of functionality or test coverage without a disclosed replacement, or a silent API/ABI break. "At new pin" says whether the defect survives in the tree this MR would ship.

# Component Commit Subject Verified defect At new pin
1 acl-manager 6daa7e419901 Issue: NET-7541 missing parameter in the inform message scripts/access_check.sh does rm -rf "$DIR" on any /cfg/etc/acl/*/ dir whose *.json cat fails or that lacks +x; subject unrelated yes (94ca16a48c13)
2 acl-manager 5604dad367c2 Issue: NET-7541 missing parameter in the inform message Installs access_check.sh to /usr/bin and runs it from acl-manager.sh start, activating the rm -rf on real images partly; init call yes, install line dropped
3 amx-cli d1f6a2f09a68 test: Non root user has full data model access (no ACL checking) (HOP-10263) test/test_cli_args/test_main.c drops cmocka_unit_test(test_cli_can_set_output_format_json) and registers test_cli_can_set_user twice yes (7f7f06cac383)
4 amxo-cg 5daee2209ae6 amxo-cg: Allow parsing odl to get config parameter value (HOP-9269) Generator key renamed to json-dm but gen_json_write_dm() still passes "json", so -Gjson-dm,<file> loses the filename and dumps to stdout; also renames -Gjson (CLI break) yes (8038690031af)
5 libamxb 2561970b058e amxb_msg: improve USP to legacy amxb_add format conversion usp_add_compat.c does atoi(strrchr(...) + 1) with no NULL check on a path taken from a peer's AddResp, and writes into the variant's string in place yes (215ca9d7)
6 libamxb 47d6b909735b feat: Add backward compatibility layer for amxb_del operations with USP DELETE amxb_msg_del_result_converter() discards the result of amxb_msg_usp_error_to_amxd_status(), so amxb_del() returns raw USP 7xxx codes no; repaired in range by 1337f81fb464
7 libamxc 9b30ac91dd64 amxc_set: Add mutual difference operation (HOP-10075) Unequal-count branches adjust flag_*->count without adjusting set->count, so amxc_set_get_count(set, NULL) returns a stale total; untested yes (e078c08dc998)
8 libamxc c68e2e4a2ae9 Allow amxc_var_set_path to create lists AUTO_ADD numeric tokens loop for(i=0;i<=index;i++) on an unbounded strtol index calling amxc_var_get_index() each pass: a.1000000.b costs O(n²) time and n allocations; also changes htable-key semantics yes (e078c08dc998)
9 libamxd 5dad2f13efc9 Add type setters for parameters (wifi) amxd_param_set_cstring_t() does free(param->value.data.s) then strdup(data): self-assignment is use-after-free, data==NULL is UB, failed strdup still returns amxd_status_ok. 13 new public setters, no tests yes (ce4148d5ffe2)
10 libamxd c860ee03fe93 amxd: heap memory optimization for flags and parameter names amxd_param_copy() sets shared_name=1 and stores source->name unowned; param->flags and amxd_param_build_description() change amxc_var_t*amxc_set_t* in public headers; test_amxd_param_delete drops 10 destroy assertions yes (ce4148d5ffe2)
11 libamxj c69c3e9f6655 [AMXJ] Add function to read JSON data from a file to a variant (HOP-12151) amxj_read_json() ignores amxj_read()'s return and amxj_read() exits on read()<0 without touching parse_state: a directory path spins forever; untested yes (47808b29b526)
12 libamxj fefa24f5ca2b amxj: return written length for amxj_write amxj_convert(), amxj_writer_set_config() and amxc_var_push_jstring_t() removed from amxj_variant.h; the deleted "Release v1.1.0" CHANGELOG entry never returns yes (47808b29b526)
13 libamxo 3828c4edd099 odl read: odl parser reads incorrect content in large odl files (HOP-9918) test_parse_list_parse_errors() and six malformed-ODL assertions deleted with no replacement; body admits heap-owned strings leak on syntax errors, no %destructor yes (ede9e06f2f03)
14 libamxp fa1fa51ad42c Issue: HOP-8397 [Amxs] Add API to monitor search path subscriptions on the client side bool verify deleted from the fully public struct _amxp_expr (callers stack-allocate amxp_expr_t), shipped v2.12.1→v2.13.0; eval semantics also reworked no; verify re-added by a694f57ea76c
15 libamxt ecde650b576a amx-cli: Pseudo terminal not properly closed on exit (HOP-8991) amxt_tty_open()'s error path calls amxt_tty_close(), but tty_fd is assigned only after six when_failed gates, so close(fd) hits calloc's 0 = stdin; close() never clears O_NONBLOCK, the stated root cause yes (76f55e74a274)
16 libsahtrace 4bcf7fd756de Release libsahtrace version sah-next-4.2_v0.1.1 src/Makefile unconditionally emits -DCONFIG_SAH_TRACE_COLORS/_SIMPLE/_USER_TRACES_ENABLED/_SYSLOG_CONSOLE=$(var) while sahtrace.c gates on #ifdef, so colours, user traces and syslog LOG_CONS are always on yes (74747f6aaffd)
17 lua-amx 2bcadfa8916e fix: Add unsubscribe method to lua bindings (HOP-11177) lamx_bus_subscribe() defers amxb_subscription_new and leaves subscription=NULL, but the new lamx_bus_unsubscribe() dereferences amx_sub->subscription->object unguarded yes (de53846578bb)
18 mod-amxb-ubus a9580943f0b8 fix: Disconnect DM signal slots on ubus disconnect to prevent use-after-free Adds amxd_dm_t* dm = amxb_ubus_ctx->dm; above the existing when_null(amxb_ubus_ctx, exit), dereferencing before the guard; the UAF fix itself is correct yes (94253172aa4a)
19 mod-amxb-ubus f71cfd1a170c feat: implement USP message handling and fragmentation support for ubus backend amxb_ubus_msg_new() sets msg->recv_msg = helper, takes the item, then amxc_var_delete(&helper); recv_msg dangles and is used by amxb_msg_handle(); original HTABLE variant leaks; malloc unchecked yes (94253172aa4a)
20 mod-amxb-ubus fa68fac4cc11 ubus: cannot read objects under Device. root path (HOP-7321) ubus_send_reply() moved into the below-threshold branch only; an oversized non-HTABLE ret logs "sending large ubus msg" and is dropped, breaking the 3-message protocol yes (94253172aa4a)
21 mod-ba-cli e02882f991d9 feat: Add Wait For Object support (NET-8097) mod_pcb_cli_free_wait_for_object_ctx() frees ctx without amxp_slot_disconnect_signal_with_priv, leaving a slot with dangling priv yes (eadc720ee6aa)
22 mod-busstats 4dc7d66433d4 Issue: HOP-5947 Amx backend: RPC to reset a plugin statistics s_reset_bus_stats() never amxc_array_delete()s the caller-owned array from amxb_list_uris(): a leak per ResetBusStats call; also omits continue after the NULL-ctx log yes (a4cd3ea9d0ae)

Twenty of the 22 carry no Signed-off-by (exceptions: #11, #18), and twenty are present in the tree the new pins would ship; #2 only partially, since the init-script call survives but the makefile install line was dropped by the pin commit itself. The two exceptions, #6 and #14, were repaired later in the same range. The BLOCKER count is the residue after adjudication, not raw rater output: raters raised 75 candidates and 53 were downgraded (see below).

Attribution stripping and DCO

checks/c1-reauthored.tsv records 206 patch-id twin pairs across 23 of 27 components: commits sharing an identical git patch-id --stable between the pre-MR baseline (old_shaorigin/main) and the new branch: the same patch re-committed under a different identity. Zero twins: acl-manager, libamxtui, libqosmodule, mod-lua-amx. 168 twins changed author on the new side; 153 had a Signed-off-by on the old side dropped on the new (143 of those also changed author); 33 have their new-side body reduced to a bare GitOrigin-RevId: trailer. Original-author domain breakdown: softathome.com 168, bot@prplfoundation.org 18, other 13, inango-systems.com 6, sagemcom.com 1.

Two concrete pairs (more in checks/c1-notes.md):

  1. libamxd, patch-id 3e52777c9b7e… (C1's validation pair, checks/c1-notes.md): old 16b107d3e56b (a sagemcom.com contributor, signed off) → new cc1b875087e3 (sahbot, softathome.com, body = GitOrigin-RevId: only).
  2. mod-ba-cli: old ff36059fe0a6 (a groupe-telnet.net contributor, signed off) → new fd6cf5931852 (sahbot).

808 of 1,013 new commits (80%) carry no Signed-off-by (checks/c2-dco.tsv);¹ 802 of 808 (99%) are one account, sahbot <support.opensource@softathome.com>, the remainder a softathome.com contributor (3), prplbot@prplfoundation.org (2), a skyworth.com contributor (1). Worst per-component unsigned rates: libamxb 169/187, libamxd 90/105, libamxp 67/70, libamxc 63/69, libamxo 59/71, mod-ba-cli 48/55.

¹ manifest.jsonl counts 810 unsigned; 2 commits (c5480ee9, 3c4f4de3) carry Signed-off-by inside the body rather than as Git trailers, so c2-dco.py's trailer-only parser excludes them.

The prplOS MR's own 4 commits are all signed off (checks/c2-prplos-mr.md): softathome.com contributors (a30b48a0, 47ec0ea6, f302925a, dac8228a). The MR submitter (inputs/mr3019.json) authored none of the four.

This is a provenance fact, not a per-commit verdict: per adjudicate/ADJUDICATION.md rule 6, no commit was rated SCRUTINIZE or BLOCKER solely for re-authorship. The point stands independently; the 153 dropped sign-offs include contributions from at least four organisations outside SoftAtHome, and a DCO chain terminating at a bot account cannot be audited back to the original author.

Jira references, trailers and descriptions lost

Across 206 patch-id twin pairs, 102 have at least one prpl Jira key on the default-side commit that is absent everywhere on the pinned-side twin.[^c9-backport] 6 lose all prpl references while acquiring a NET reference, and 1 loses a body or trailer reference that survives in the subject. 158 pairs drop trailer keys; the 178 pairs adding keys mostly add GitOrigin-RevId (168), with Issue (64), References (10), Co-authored-by (1) and Signed-off-by (1) also added, in overlapping sets.

21 pairs lose all non-trailer description lines and 15 retain fewer lines; shrinking is a line-count signal, not proof of semantic loss. This reconciles with the earlier 33 GitOrigin-only bodies: 14 already lacked a description on the old side, while 2 other pairs lose their description without a GitOrigin-only body, so 33 − 14 + 2 = 21. Among distinct keys on the default side, 9 of 67 PPW, 5 of 11 PCF, and 1 of 2 PPM keys survive somewhere in their corresponding twins; no LCM keys occur in scope.

For a groupe-telnet.net contributor's libamxd attributed commit, the sahbot copy drops the sign-off and References: PCF-1062, adds Issue: NET-6836, and keeps the description, rewritten around the NET reference. The attributed commit's author date is 2025-06-12 and the copy's is 2025-05-12 because SoftAtHome merged internally before the public prpl MR merged; authorship, sign-off and Jira reference establish the attributed commit as the original despite that date order.

For a sagemcom.com contributor's libamxd attributed commit, the sahbot copy drops the sign-off, Fixes: PPW-646, and the entire problem/solution description. Only the subject still carries PPW-646; the body is a bare GitOrigin-RevId line.

Of the 206 new-side twins, 172 carry a terminal GitOrigin-RevId reference, but 0 of those revision IDs resolve as local commits or are reachable from the fetched prpl-foundation origin/* histories, so these back-pointers cannot recover the original context from the audited public history (checks/c9b-gitorigin-resolvable.tsv). Git recognizes 169 as proper trailer-block entries; 3 are terminal revision lines attached to prose or malformed blocks, included explicitly in this reference check.

Reproduce with python3 checks/c9-jira-trailers.py, python3 checks/c9b-gitorigin-resolvable.py and python3 checks/followup-integration-checks.py. The per-component and prefix tables are in checks/c9-jira-trailers.md; trailer-key churn depends on its documented parser conventions, while whole-message Jira counts do not. The GitOrigin check uses cached origin refs and git cat-file -e with lazy fetching disabled; it makes no claim about unfetched or private histories.

[^c9-backport]: The 102 includes one direction-inverted pair: libamxb 84751576 on the default branch is a prpl-side backport of 71ca42da, with a cherry-pick footer and a prplfoundation.org sign-off marked [backport]. In this pair the backport added PPM-3259; the stable-side original did not drop it. The 102 is therefore a directional message-comparison count, not uniformly a count of references stripped by re-import. Reproduce the direction check with python3 checks/followup-integration-checks.py.

Pin/supply-chain integrity

All 27 of 27 changed packages switch PKG_SOURCE_URL from a tagged tarball to a .git clone with PKG_SOURCE_VERSION set to a branch name, and all 27 add PKG_MIRROR_HASH:=skip where none had it before (checks/c5-feed.md, inputs/ranges-notes.md §3). Every build now fetches whatever the branch resolves to at fetch time, not a hash-verified commit: the 1,013 commits audited here are those branches as of the audit, and a later push to any of the 27 silently changes what prplOS builds with no diff to !3019.

PKG_VERSION anomalies (checks/c5-feed.md): libamxb goes from v4.17.1 to v4.11.8, numerically lower. A same-run prpl-only commit on mod-amxb-ubus (b5c8773a788c, "Set minimum libamxb version to 4.11.8") cites the same figure, so this may not be a copy/paste error; it is not resolved here. libsahtrace's new PKG_VERSION uses an underscore (prpl_stable-v11.44.1) where the other 22 use a dash. In Config.in, tr181-qos renames SAH_AMX_TR181_QOS_ORDER..._START_ORDER and adds ..._STOP_ORDER=51, a rename that can silently drop a defconfig fragment referencing the old name; amxrt's SAH_SERVICES_AMXRT_STOP_ORDER=90 and libsahtrace's SAH_USER_TRACES_ENABLED=y are additive.

The description's top table cites two hashes that resolve nowhere: amx_v11@f4904420, dev_forward_port_qos_optimizations_main@81364654 (rc=128 on git rev-parse --verify, 0 matches in git log --all); the actual pins are 623ddb685006… (subject: "PROVISORY: libamxb: set dummy version") and 7eba715c3a1a… (checks/c6-description.md §1). Its per-component branch-head table was spot-checked and is accurate. Its claim of "Three files, two commits" is not: the diff is 12 files, 4 commits: 2 profile YAMLs plus 10 cram-test files, with the 7 per-target 005-build-config.t files each touched by two of the four (checks/c6-description.md §2).

All 6 dependency MRs' head SHAs match exactly (feed_amx!124, feed-prplos!994, libqoscommon!6, libqosmodule!5, libqosnode!4, tr181-qos!40), yet all 6 remain opened (checks/c4-pins.tsv) despite the description's "Every MR below must merge before this one." None of the 23 ambiorix components has its own MR from prpl/stable-v11.44.1 into main (component_mr_check rows, all no). A live force-push check found no drift; all 27 branch heads still match components.tsv, though a tip commit labelled "PROVISORY" signals a standing amend/force-push risk that has not fired yet (checks/c5-feed.md §2).

Default-branch coverage of the pinned branch

Of 171 default-branch commits with no exact patch-ID twin in the pin, 64 have their patch changes present verbatim (the main-side patch reverse-applies to the pinned tree), 36 have a near twin, and 71 remain unresolved by the automated checks. These are successive checks for content presence, not a count of missing commits. Under those checks, 21 of 23 components moved to prpl/stable-v11.44.1 have unresolved rows; confirmed missing main-side code fixes are in amx-cli and amxo-cg. The four QoS components use dev_forward_port_qos_optimizations; they and libamxtui have no exact-twin misses, and acl-manager's misses are all present verbatim.

Component no-exact-twin present-verbatim near-twin unresolved confirmed-missing-code
acl-manager 3 3 0 0 0
amx-cli 12 5 3 4 1
amx-fcgi 6 3 1 2 0
amxo-cg 13 1 7 5 1
amxrt 9 4 1 4 0
libamxa 9 3 2 4 0
libamxb 17 6 4 7 0
libamxc 8 5 1 2 0
libamxd 7 2 1 4 0
libamxj 4 1 2 1 0
libamxm 4 1 1 2 0
libamxo 7 4 1 2 0
libamxp 6 4 0 2 0
libamxrt 7 4 0 3 0
libamxs 7 4 1 2 0
libamxt 5 2 1 2 0
libamxtui 0 0 0 0 0
libsahtrace 11 1 1 9 0
lua-amx 18 6 4 8 0
mod-amxb-ubus 3 0 1 2 0
mod-ba-cli 9 3 2 4 0
mod-busstats 2 0 1 1 0
mod-lua-amx 4 2 1 1 0
libqoscommon 0 0 0 0 0
libqosmodule 0 0 0 0 0
libqosnode 0 0 0 0 0
tr181-qos 0 0 0 0 0

The unresolved column retains the automated absent bucket so the coverage chain remains reproducible; the last column counts only hand-confirmed missing code fixes. It does not count near twins as proven equivalents or unreviewed rows as missing content.

Hand review of the 16 unresolved code rows finds 10 present in modified form, 3 libsahtrace release reverts the pin never received, and 2 confirmed missing main-side fixes. The remaining 1 row is libsahtrace cfa8e0ad7d, a release change in that same reverted chain: the pin still retains the empty-macro block that release removed, so it is not counted as a third confirmed missing fix. Across the selected hand-review set, 19 of 26 automated absent rows are present in modified form; that set comprises all unresolved code rows plus sampled non-code rows, not a random sample of all unresolved commits.

The libsahtrace reverts 3d4538bdd2, 0f54024ec2 and 91425b02d3 remove release code on the default branch that the pin still retains. This is a reverted-code divergence in the opposite direction from a missing fix and requires a separate decision; the hand evidence is in notes/followup-verify2.md, Part 2.

The mod-amxb-ubus pair 96ef54b14173 and d30df3cbe9c0 shows why patch IDs overcount absence: a trailing comma survives the whitespace-insensitive comparison, and changed registration context defeats reverse-apply. Yet amxb_ubus_reset_stats and its .reset_stats registration are both present in src/amxb_ubus.c at the pin; the automated result is a subject-based near twin, not missing functionality.

libamxd 07e50d86 is a near twin of pinned c860ee03, which implements the same heap optimization more broadly: 13 files, +187/-203 at the pin versus 10 files, +37/-67 on main. The optimization is present at the pin in a different implementation; the absence of an exact patch-ID twin does not establish a missing optimization (notes/followup-verify2.md, Part 3).

libamxd as the worked example: 27 commits are unique to its default branch and 105 to the pin. The merge-base is d5bad5c7c933 (author date 2025-01-07), and the first commit unique to the stable side is dd72f2f6b776 by sahbot, dated 2025-01-13, six days later. All 23 fetched prpl/stable-v11.44.1 tips still equal the pins recorded in the MR, so the pins had not moved when this audit ran; 9 components also carry a prpl/stable-v12 branch, and the 4 QoS components are pinned on dev_forward_port_qos_optimizations instead. Every component resolves origin/HEAD to main (0 fallbacks) and no default branch has merge commits outside the pin (0 default-only merges).

Why the branch exists: the bot-authored start soon after divergence and the re-authored twins are consistent with a bot-driven mirror/import branch, an inference from history rather than evidence of the maintainers' intent. C4 records no component MR from the stable branch into main, while QoS uses the separate forward-port branch. The branch name does not make it the repository's real default or establish that it preserves all default-branch fixes.

The earlier 0/32 "fast-forward into default" figure describes stable-tip containment: none of those stable tips is reachable from its default branch. This is an ancestry observation, not a content-absence count or a test of whether a default branch can advance to its stable tip; checks/c10b-branch-ancestry.tsv records both directions.

C10 checks exact patch IDs; C10b seeds a temporary index from the pin and tests reverse-apply, then searches for whitespace-insensitive patch or normalized-subject twins. Neither a failed apply nor a near-twin heuristic proves semantic absence or equivalence. Reproduce the automated chain with python3 checks/c10b-near-twins.py and the per-component table, branch ancestry and aggregation of the verifier's recorded hand judgments with python3 checks/c10b-correction.py; full methods are in checks/c10b-near-twins.md.

Prpl-only commits riding along

checks/c8-prpl-only.tsv lists 105 commits on the 23 ambiorix components' prpl/stable-v11.44.1 branches that are not reachable from each component's SoftAtHome upstream tag, matching inputs/baseline-b.tsv exactly, 23/23. (The 4 QoS components have no public upstream and are excluded per PLAN.md.) Of the 105: 4 unsigned, 10 touch test paths, 65 touch CI config (checks/CHECKS.md §C8). Matching subjects against recurring scaffolding patterns (baf: add initial baf file, ci: restore prpl ci, ci: baf: update autogenerated files and similar) classifies 83 of 105 as CI/BAF scaffolding, 22 as non-scaffolding.

component prpl-only commits unsigned CI/BAF scaffolding
acl-manager 4 0 4
amx-cli 5 0 4
amx-fcgi 5 0 4
amxo-cg 4 0 4
amxrt 4 1 3
libamxa 4 0 4
libamxb 5 0 4
libamxc 6 1 3
libamxd 6 0 3
libamxj 4 0 4
libamxm 4 0 4
libamxo 7 0 3
libamxp 2 0 2
libamxrt 4 0 4
libamxs 3 0 3
libamxt 5 0 4
libamxtui 2 0 2
libsahtrace 6 2 4
lua-amx 7 0 4
mod-amxb-ubus 6 0 4
mod-ba-cli 4 0 4
mod-busstats 4 0 4
mod-lua-amx 4 0 4
total 105 4 83

Notable non-scaffolding commits:

The picture is not "a feature branch smuggled in behind a version bump": 83 of 105 prpl-only commits are CI and packaging scaffolding, which a fork import is expected to carry.

Test and CI impact per component

prplOS-level test expectations

Of the MR's 12 changed files, 10 are under .gitlab/tests/cram/ (inputs/mr3019-diffs.json). The one file the description names, 046-qos-classification.t, changes as described: ForwardingPolicy goes from 16128 to 250 and the firewall mark from 0x3f00/0x3fc0 to 0x3e80/0x3fc0 (ip rule shows class 0xfa = 250), consistent with "0–255, masked and shifted." Two files go beyond the description:

The remaining 7 files are per-target 005-build-config.t fixtures tracking the CONFIG_SAH_AMX_TR181_QOS_ORDER..._START_ORDER rename plus new ..._STOP_ORDER, CONFIG_SAH_USER_TRACES_ENABLED and CONFIG_SAH_SERVICES_AMXRT_STOP_ORDER symbols, inconsistently across targets.

Component test content, old pin vs new pin

Static test-content metrics (checks/c3-test-metrics.tsv; cases = cmocka unit_test + setup_teardown + bare unit_test registrations):

component test files (old→new) cases (old→new) asserts (old→new) skips (old→new) commented-out (old→new) .gitlab-ci.yml changed
acl-manager 34→41 63→84 121→177 0→0 0→0 False
amx-cli 55→55 359→359 231→231 0→0 0→0 False
amx-fcgi 75→79 366→375 208→218 0→0 0→0 False
amxo-cg 66→66 123→123 122→122 0→0 0→0 False
amxrt 8→8 9→9 3→3 0→0 0→0 False
libamxa 122→134 477→492 890→934 0→0 0→0 False
libamxb 54→165 294→1798 962→2719 0→20 0→0 False
libamxc 177→183 1286→1331 5944→6008 0→2 0→0 False
libamxd 181→181 1108→1132 5337→5447 0→0 0→0 False
libamxj 23→24 102→126 294→327 0→0 0→0 False
libamxm 9→9 55→55 213→213 0→0 0→0 True
libamxo 170→170 537→537 994→987 0→0 0→0 False
libamxp 84→85 785→815 2433→2506 0→0 0→0 False
libamxrt 51→51 312→315 362→370 0→0 1→1 True
libamxs 31→31 144→171 773→962 0→0 0→0 True
libamxt 37→37 261→261 792→792 0→0 0→0 False
libamxtui 29→29 124→124 347→347 0→0 0→0 False
libsahtrace 0→0 0→0 0→0 0→0 0→0 True
lua-amx 121→121 421→424 574→576 0→0 0→0 False
mod-amxb-ubus 38→70 85→106 113→139 0→0 0→0 True
mod-ba-cli 33→40 380→407 256→278 0→0 0→0 False
mod-busstats 9→9 3→3 14→14 0→0 1→1 False
mod-lua-amx 11→11 21→21 17→17 0→0 0→0 True
libqoscommon 0→0 0→0 0→0 0→0 0→0 True
libqosmodule 13→13 18→18 58→58 0→0 0→0 False
libqosnode 0→0 0→0 0→0 0→0 0→0 True
tr181-qos 77→77 473→485 1233→1236 0→0 0→0 False
sum of 27 1508→1689 7806→9571 22291→24681 0→22 2→2 8/27 changed

In aggregate, test content grows: +181 files, +1,765 cases, +2,390 assertions. Four components tripped raw threshold rules, all benign on follow-up (checks/c3-flags.md): libamxa's 3 "deleted" test files are renames into test/common/; libamxb's skips 0→20 are all in test/amxb_msg_compat/, absent at the old sha; new coverage shipping partly self-skipped, not skips added to passing tests; libamxc's 0→2 is the same shape smaller; libamxo's asserts 994→987 trace to two bug fixes reworking assertions.

These aggregates do not contradict findings 5–6: a net-positive assertion count over 1,013 commits says nothing about the six malformed-ODL cases deleted in libamxo 3828c4edd099 or the JSON output-format case dropped in amx-cli d1f6a2f09a68, both targeted removals inside a growing suite. .gitlab-ci.yml changed for 8 of 27, baf.yml for 15 of 27; Makefile test: targets and coverage keywords are unchanged everywhere.

CI template pinning (checks/c7-ci-template.md): 25 of 27 components floated on the unpinned shared template at the old sha, 26 of 27 at the new. Four changed include source: libsahtrace, libqoscommon and libqosnode moved off a separate, also-floating SoftAtHome template onto the prpl-foundation one, and mod-lua-amx went from none to the shared one. No include is version-pinned, so a change to that template's main changes every component's pipeline at once.

Coverage and JUnit data (inputs/ci-compare.tsv) is effectively absent: old_coverage and all old test totals are blank or 0 for all 27; new_coverage is populated for 2 (libqosmodule 100.00%, tr181-qos 90.00%) and new_tests_total for none. The API data cannot confirm that any of these suites passed at either pin.

Memory relevance summary

MR !3019 is presented in the context of memory work, so, plainly: of 1,013 commits, 2 are classified memory-optimization and 9 performance: 1.1% of the import. The two memory commits:

The 9 performance commits: libamxb 8acf31f194ed (OK) and f5d182e39b6c (SCRUTINIZE); libamxc 2e880eec321b, 7e4efc003c35 (OK) and c81a575cd8ba (SCRUTINIZE); libamxd 7e7f6a0f1cc6 (OK); libamxrt 26f27be2c6f4 and 49daeb3e1044 (SCRUTINIZE); tr181-qos a6d4e48a2702 (SCRUTINIZE). Separately, the four tr181-qos ODL commits behind the seven removed Device.QoS.Classification.* parameters describe themselves as RAM optimisations; they are categorised api-change because their diff is a data-model surface change.

No dynamic measurement was done (PLAN.md decision 5): no valgrind or massif run, no RSS sampling, no before/after footprint comparison on any target. Nothing here supports or refutes a claim that the v11.44.1 upstep reduces memory use on a prplOS device; that needs a separate measured comparison against baseline A. What this audit can say is that the memory-motivated content of the import is two commits, one of them a BLOCKER, plus four unmeasured data-model reductions.

Per-component notes

Ordered by BLOCKER count, then SCRUTINIZE. "api" = api-change + abi-change; "unsigned" = no Signed-off-by trailer; "twins" = patch-id re-authored pairs (C1).

component commits OK SCR BLK api feat test unsigned twins notable
mod-amxb-ubus 48 39 6 3 2 3 4 38 5 Three BLOCKERs, all live at the new pin: dangling recv_msg, dropped oversized replies, deref-before-guard. Test files 38→70
libamxb 187 114 71 2 20 29 26 170 22 Largest component by far and 71 SCRUTINIZE. New amxb_msg USP compat layer landed incrementally: several intra-range commits do not compile in isolation (4191bd6249f6, cef27c69179b); PKG_VERSION drops v4.17.1→v4.11.8; amxb_subscription_new symbol removed, source compat restored later by a #define
libamxd 105 80 23 2 13 4 7 90 20 Both BLOCKERs are public-API commits (5dad2f13efc9 13 untested setters with a UAF; c860ee03fe93 the memory-optimization commit). 13 api/abi commits and 21 touching public headers
libamxc 69 56 11 2 4 7 7 63 22 9b30ac91dd64 stale set->count; c68e2e4a2ae9 unbounded AUTO_ADD index loop. Also c02cd6fe219c (base64 padding sentinel at index 59 instead of 61) and f7edfe1a5ae3 (1-byte OOB read in naturalstrcmp), both SCRUTINIZE
libamxj 27 17 8 2 5 1 0 19 3 Smallest component with 2 BLOCKERs, and the worst api-change density (5/27). Zero test-change commits against 5 api-change commits
acl-manager 21 11 8 2 1 3 0 12 0 The rm -rf pair. No re-authored twins. Test files 34→41, so coverage grew, but no test accompanies access_check.sh
libamxp 70 51 18 1 4 4 2 67 24 Highest twin count (24) and 67/70 unsigned. BLOCK