Skip to content

feat(qos): add N5 QoS core processing and N7 commit and notify - #2

Open
tariromukute wants to merge 42 commits into
developfrom
feat-n5-qos-support
Open

feat(qos): add N5 QoS core processing and N7 commit and notify#2
tariromukute wants to merge 42 commits into
developfrom
feat-n5-qos-support

Conversation

@tariromukute

@tariromukute tariromukute commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Merge Requests on GitLab

Description

The use case: an Application Function asks the 5G core for better-than-best-effort treatment for one media flow — "4 Mbps down, guaranteed, under 150 ms" — and the network delivers it on a PDU session (an established user data connection) that is already up, without interrupting the user's traffic.

Structurally the PCF is a policy decision service: an HTTP/2 JSON API (N5) over one CRUD resource (an app-session = one AF quality request for one flow), which validates against operator limits and the subscriber's contract, translates into a policy document, attaches it to the right per-user session record, and pushes the result over a second HTTP/2 call (N7) to the SMF, which programs the data plane (UPF).

  ┌────┐   N5 / TS 29.514              ┌─────────────┐   N7 / TS 29.512       ┌─────┐   N4    ┌─────┐
  │ AF │ ────────────────────────────► │     PCF     │ ────────────────────►  │ SMF │ ──────► │ UPF │
  └────┘  POST/GET/PATCH/DELETE        │  (this MR)  │  SmPolicy UpdateNotify └─────┘         └─────┘
   API      /app-sessions              └─────────────┘   (outbound call)      applies       moves the
  client   (our inbound API)                                                 our policy      packets

  [1] BIND       find the live N7 association (per-user session record) for (ueIpv4, supi, dnn)
   │             ──► returns its current decision AND its version
  [2] DERIVE     MediaComponent ──► QosData + PccRule (SDF filters) + QosCharacteristics
   │             = a treatment (bandwidth/priority/latency class) + packet filters match rule.
  [3] AUTHORIZE  allowed 5QI · ARP range · per-flow MBR cap · GBR<=MBR · cumulative non-GBR vs.
   │             subscribed Session-AMBR  (permitted profile? priority in range? floor below
   │             ceiling? do all flows together fit the user's total budget?)
   │             └─ reject ─────────► 403 to AF, nothing committed anywhere
  [4] VALIDATE   referential integrity — no rule may point at a treatment absent from the document
   │             └─ inconsistent ───► 500 — PCF-side fault (code or config), not the AF's
  [5] COMMIT     apply the delta to the association ONLY IF still at [1]'s version
   │             (compare-and-swap: write only if nobody wrote since we read)
   │             └─ version moved ──► re-derive (restart) from [2] against the new base (max 3 tries)
  [6] NOTIFY     full SmPolicyDecision ──► SMF ──► UPF installs / modifies the QoS flow
        ├─ applied ──────────────► 201 (200 on PATCH) to AF; app-session ledger updated
        ├─ temporary / ambiguous ─► bounded retry with backoff. Never a rollback.
        └─ permanent rejection ───► compensating rollback (per-key staleness check), then AF
                                    failure notification (next phase)

Steps [2]-[4] are pure, which is what makes the retry at [5] safe. PATCH and DELETE walk the same six steps: PATCH re-derives the same medCompN (the AF's own media-component key, reused as a stable id) so a repeat request upserts rather than duplicates; DELETE derives a removals-only delta from the session's ledger.

Two properties shaped the design:

  • Concurrent AF requests on one PDU session are serialisable — two AFs updating the same user's session at once cannot corrupt each other's result, nor together exceed a limit each satisfied alone. Policy Authorization pushes an incremental SmPolicyDelta (only the keys it touched) applied under an optimistic version-CAS (compare-and-swap), re-deriving against the committed base on conflict (step [5]).
  • An SMF rejection never leaves the PCF and SMF disagreeing — the notify response is classified per TS 29.512 Table 5.7.3-2 into retry-eligible vs. confirmed-permanent: bounded retry for the former, staleness-checked compensating rollback for the latter. Deliberately biased so an ambiguous failure (timeout, dropped connection, unmodeled status) never rolls back — undoing a change the SMF did apply is worse than retrying one it didn't.

Legend: ✅ real · 🟡 partial · 🔶 mock · ⬜ deferred to a later MR in this series

Phase 1 — Core QoS Processing ([QOS])

  • ✅ Session binding (sm_session_binding) — resolves which established user session an AF request applies to, keyed on UE IP / subscriber id / data-network name.
  • handle_qos_requirements() — templated over MediaComponent (create) and MediaComponentRm (PATCH), the generated create/update variants of one wire object, so there is one implementation rather than two that drift.
  • create_qos_data_from_media_component() — real QosData + PccRule per TS 29.513 §7.3.3: MBR (ceiling) from marBw/summed per-SDF, GBR (floor) from mirBw, 5QI from a latency heuristic, SDF filters from medSubComps — or the operator's preset when qosReference resolves. Deterministic medCompN-keyed ids, so a PATCH modifies in place with precedence reused, not reassigned. resPrio → ARP priorityLevel is not mapped (see Known Issues).
  • create_qos_characteristics() — real entry (priority level, packet delay budget, packet error rate, resource type) for non-standardized 5QIs; a no-op for standardized ones, whose meaning every downstream node already knows.
  • validate_qos_authorization() — dynamic-5QI allow-list, ARP range, per-flow MBR cap, cumulative non-GBR vs. authorized Session-AMBR, GBR≤MBR. The one real policy call: when the network never sent the user's bandwidth budget, allow or deny? Configurable, defaults to allow per the spec's own wording.
  • validate_and_merge_decision()insert_or_assign so other app-sessions' entries survive, plus referential-integrity checks (no dangling refQosData/refTcData, no duplicate ids, unambiguous precedence).
  • validate_policy_decision() — pre-notification gate on both create and PATCH; a self-inconsistent decision is our bug (500), never sent downstream.
  • ✅ QoS modification via PATCH (§1.6) — modify in place, add, or remove via fStatus: REMOVED (including one sub-component of a retained component); all three re-run create's authorization/merge/validation gates.
  • ✅ RFC 7396 JSON Merge Patch of the stored ascReqData (merge_patch_context()) — scalars replaced, components and sub-components merged/added/removed, so a GET after a PATCH reflects the change.
  • GET /app-sessions/{id}; POST201 + Location + negotiated ascRespData.suppFeat; PATCH200 + merged context; Content-Type enforced on every body-carrying endpoint (415 otherwise).
  • ✅ Operator QoS policy is config-driven, not hardcoded (see Configuration Updates).
  • ✅ App-session model: app_session aggregate root with a qos_context ledger holding identifiers only — the QosData/PccRule payload's single source of truth stays the association's decision, so the two cannot drift. The ledger is written only after a successful commit; derivation targets a scratch object, so a rejected or retried attempt leaves no trace.
  • app_session_storage over a generic injectable crud_store — restart-safe UUID ids, 1:N association_id → app-sessions index. app_session_record fixes the durable schema now so MR 3 is a backend swap at this seam; this MR ships the in-memory backend.
  • 🟡 N5 events-subscription endpoints ({PUT,DELETE} /app-sessions/{id}/events-subscription, §4.2.6) still return 404 — held for MR 2, since nothing consumes a subscription until the notification infrastructure exists.

Phase 2 — Cross-Service Coordination ([QOS-SMF])

The PCF's two internal services (Policy Authorization owns the AF-facing API; SM Policy Control owns the session records and the downstream call) communicate over an in-process boost::signals2 event bus rather than calling each other directly.

  • SmPolicyDelta — incremental change set over qosDecs/pccRules/qosChars/traffContDecs, with compute_sm_policy_delta()/apply_sm_policy_delta(). Pushing a delta rather than a whole decision removes the lost-update race on concurrent PATCH: unchanged keys are omitted, so an apply only touches what that request changed. Same reasoning as PATCH rather than PUT on a shared resource.
  • ✅ Optimistic (version-CAS) commit — binding returns the decision and its version; handle_commit_decision_request() applies the delta copy-on-write under the association lock only if still at that version, else returns current version + decision for the caller to re-derive (decision_applier::apply, 3 attempts). Closes two races: the ordinary lost update, and the subtler one where two requests each pass a cumulative check against the same stale snapshot — two AFs requesting 60 Mbps each against a 100 Mbps Session-AMBR can no longer both succeed.
  • ✅ Commit split from notify — CAS+persist and notify are separate operations; the blocking SMF round-trip runs off-lock on an immutable snapshot [CP.22, the C++ Core Guidelines rule against holding a lock across a blocking call], and the notify re-reads live state. Necessary because the PCF runs several HTTP workers, so another request can commit to the same association between our commit releasing the lock and our notify reacquiring it.
  • send_sm_policy_control_update_notify() — delivers the full decision (pccRules, qosDecs, qosChars), confirmed against a live SMF. Full-not-delta is deliberate, not an oversight: the delta is PCF-internal because the partner SMF diffs the full object itself and a partial SmPolicyDecision on the wire is an interop hazard. Internally incremental, externally complete.
  • classify_smf_notify_response() — maps (HTTP status, body) to applied / permanent_rejection (only PCC_RULE_EVENT, the one cause proving a retry is futile) / temporary_rejection / transport_ambiguous per Table 5.7.3-2. Handles the awkward 200-with-PartialSuccessReport (success status, failure body) and defaults unmodeled statuses to ambiguous. Pure function, so the whole matrix is unit-tested without mocking HTTP.
  • retry_drain_queue — re-attempts temporary/ambiguous notifies off the task_tick heartbeat with exponential backoff, per-entry TTL and a hard cap; each attempt re-fetches live state. Never rolls back. Both bounds present so it cannot grow or retry forever.
  • ✅ Compensating rollback — pending_rollback_tracker records what each commit would undo; compute_rollback_delta() reverts a key only if unchanged since that commit (else skipped and logged), which stops a blind revert orphaning a rule a later request depends on; perform_compensating_rollback() always re-reads live state, never the tracker's stale snapshot. The rollback re-commits through the same commit+notify path — one path, no special case to keep in sync.
  • ✅ Inline rejection (same attempt) and delayed rejection (sm_policy_update_failed from the drain) converge on one compensate_if_pending(), so there is nothing to race.
  • ✅ Recovery bounds config-driven (pcf.notify_failure_recovery); post-commit snapshot persisted to policy storage off-lock; association decision version-bumped copy-on-write.
  • 🟡 Cross-service PCC-rule coordination — collision avoidance by construction (id prefix + reserved precedence band, duplicate ids rejected on merge); active conflict detection and an SM-side convention are not built.
  • 🔶 notify_af_qos_update_failed() — logs-only stub kept on the rollback path so the call site, arguments and tests are ready for MR 2 to fill in.
  • ⬜ Deferred, each with its rationale in Known Issues: resource-management coordination (§2.4), §6.1.3.7 pre-emption-based conflict handling, AF-notification coordination (§2.6, MR 2), decision history, and qosMonDecs (Phase 4, MR 2).

Tested Environment (e.g., radios, interfaces, CN)

Full OAI 5GC under Docker Compose from oai-cn5g-fed (docker-compose-basic-nrf-qos.yaml + docker-compose-ueransim-qos.yaml): NRF, UDR, UDM, AUSF, AMF, SMF (oai-smf:qos-mod), UPF (oai-upf:fix-pfcp-mod), this PCF, UERANSIM gNB + 2 UEs, curl-based AF container.

  • Unit tests — 247 GoogleTest cases, 20 suites, all passing. New in this MR, along with the test infrastructure itself (BUILD_TESTING=ON → the pcf_unit_tests target, discovered by CTest). Build via the Docker checks stage if a native build hits the pre-existing missing-<cstdint> issue in the common submodule.
  • End-to-end — pa_app_session_tests.py, 19/19 assertions passing against the live stack. New in this MR; walks the full app-session lifecycle (create → read → modify in place / add / remove → delete) plus a deliberately invalid modification that must be rejected without side effects.

Configuration Updates (e.g., files, parameters)

All blocks optional; omitting one keeps documented defaults.

  • etc/config.yamlpcf.local_policy.qos_reference_path (operator QoS presets, e.g. OAI_QOS_GBR_VIDEO_1).
  • etc/config.yamlpcf.qos_authorization: allowed_dynamic_5qi, max_flow_mbr_ul/dl, max_session_ambr_ul/dl, reject_on_missing_subscription. Defaults permissive (no cap / any 5QI / fail-open).
  • etc/config.yamlpcf.notify_failure_recovery: retry_drain_ttl_seconds, retry_drain_max_entries, max_notify_retries, retry_backoff_initial_ms, rollback_tracker_ttl_seconds, rollback_tracker_max_entries. Unprescribed by TS 29.512/29.514, so operator-tunable; defaults 30 s TTLs, 10 000-entry caps, 3 retries, 500 ms backoff doubling.
  • New file etc/policies/qos_references/oai_qos_references.yaml. Configured as pcf.local_policy.qos_reference_path
    (default: /openair-pcf/policies/qos_references)
  • Build: -DUSE_ODB (default OFF) gates all ODB/MySQL code and linkage.

Known Issues / Limitations

Scheduled — resolved by a later MR in this series

Limitation Resolved by
AF notification + QoS monitoring absent MR 2. A subscription has nothing to feed until the notification path exists.
App-session / binding state in-memory only MR 3. Schema and storage seam are already fixed, so it is a backend swap, not a redesign.
No explicit QoS change-detection step — modification works by idempotent re-derivation, so nothing reports which attributes changed MR 2, which needs a change summary for its notifications.
Asynchronous, SMF-initiated rule-failure reports are not handled — a separate Npcf_SMPolicyControl_Update carrying ruleReports is accepted and discarded, so an SMF that accepts our notify and only later fails to install the rules leaves the PCF believing the QoS is active, with no rollback and no AF notification. TS 29.512 §4.2.4.15, §4.2.4.7 (ruleStatus=INACTIVE, failureCode=RES_ALLO_FAIL), carried in §5.6.2.19; AF-facing half is TS 29.514 §4.2.5.8 AF half lands with MR 2. Not a quick fix — correlation is keyed on decision version, which these reports don't carry, and the 30 s rollback-tracker TTL is likely shorter than the SMF's round-trip to the RAN.

Accepted — remain until a future refactor, feature, or upstream fix

Limitation Why it stays
No cross-service PCC-rule conflict detection/resolution (§2.2) Collision avoidance is by construction (id prefix + reserved precedence band). Active detection, an SM-side band, and a policy for when an AF rule outranks a base rule are all unbuilt.
Precedence can leave its reserved band Allocated as 1000 + <monotonic uid>, so past 1000 rules it exceeds 1000-1999. Harmless today (nothing enforces the SM-side band either).
No slice-level resource admission control (§2.4) No slice pools, bandwidth accounting or exhaustion handling. The only capacity gate is the per-session cumulative non-GBR vs. Session-AMBR check.
No TS 23.503 §6.1.3.7 pre-emption-based conflict handling Over-limit requests are rejected (403), never resolved by pre-empting a lower-priority service.
Compensating rollback is reference-blind, and the rollback path skips validation A key is reverted only if unchanged since the commit — correct per key, but decided per key rather than as a consistent set. If a commit created a QosData plus a PccRule referencing it and another writer later changed only the rule, the rollback removes the QosData and skips the rule, leaving a dangling reference — and perform_compensating_rollback()'s derive does not call validate_policy_decision() (unlike the POST/PATCH derives), so that decision is committed and sent to the SMF. Needs the pre-notification gate on the rollback path plus a reference-aware pruning pass. Requires concurrent writers and a confirmed permanent rejection, so it is rare today and largely masked by the 408 row below; expect it to become reachable once that is fixed.
Binding by UE IPv4/SUPI only DNN-based binding not implemented.
PartialSuccessReport read shallowly Only the top-level failureCause routes retry-vs-rollback, so a partial failure is attributed to the whole commit rather than the rules the SMF named. TS 29.512 §4.2.3.16; types at §5.6.2.27 / §5.6.3.8 / §5.6.3.9.
resPrio → ARP priorityLevel not mapped Blocked twice over: the generated ReservPriority model is empty (an upstream oai-cn5g-common-src generator defect, not introduced here), and TS 29.513 Table 7.3.2-1 leaves the mapping itself undefined ("application specific algorithm"). preemptCap/preemptVuln are unaffected.
minDesBwDl/Ul not read Gated behind the IMS_SBI feature (TS 29.514 Table 5.8-1), which this PCF does not negotiate — and that feature also covers unrelated charging behaviour.
desMaxLoss not read Carries QoSHint/FLUS applicability, also unnegotiated, and the spec prescribes no mapping formula. Should be taken on together with desMaxLatency (read today as best-effort under the same gate).

Checklist

  • Code follows project coding standards.
  • Relevant tests have been added or updated. (247 GoogleTest cases across 20 suites + the end-to-end lifecycle script; see Tested Environment)
  • Documentation has been updated where necessary. (CHANGELOG.md, docs/FEATURE_SET.md — N5 now partially supported, N7 UpdateNotify supported — and ci-scripts/tests/README.md for the new lifecycle script)
  • Configuration changes have been validated in the target environment(s). (exercised live via the docker-compose stack above)

Log files or packet captures (PCAPs)

Additional Notes

The QoS feature on PCF is split into three MRs.

Why the split, and why these seams. The original branch carried four phases plus persistence in ~40 commits — not reviewable in one pass. Each MR is cut where the code already has a seam, so none leaves a half-built abstraction:

  • MR 1 (this MR) — everything needed to decide and enforce QoS: N5 in, N7 out, correct under concurrency and under SMF rejection. Independently deployable; the AF simply gets no asynchronous notifications.
  • MR 2 — hangs off call sites this MR establishes: notify_af_qos_update_failed(), setup_qos_monitoring(), the commented-out AF/monitoring signal typedefs in pcf_event_sig.hpp, and the af()/mon() aspect slots reserved on app_session.
  • MR 3 — swaps the crud_store backend injected in pcf_app.cpp against this MR's app_session_record. Last on purpose: the schema should be shaped by all the state Phases 3-4 add, not migrated twice.

@tariromukute tariromukute added do-not-trigger-ci Indicates that CI should not be triggered. help wanted Extra attention is needed labels Jul 30, 2026
@tariromukute
tariromukute requested review from arora-sagar, phine-lukas and sgarg00 and removed request for phine-lukas and sgarg00 July 30, 2026 14:14
@tariromukute tariromukute self-assigned this Jul 30, 2026
@sgarg00
sgarg00 requested review from thinhnt1983 and removed request for arora-sagar July 30, 2026 14:16
@tariromukute
tariromukute marked this pull request as draft July 30, 2026 14:17
@sgarg00 sgarg00 added the imported-from-gitlab Items migrated from GitLab label Jul 30, 2026
@phineSte

Copy link
Copy Markdown

Sequence chart from GitLab MR

sequenceDiagram
    participant AF as Application Function
    participant API as api-server
    participant PA as pcf_policy_authorization.cpp
    participant EVENT as pcf_event.hpp
    participant SMPC as pcf_sm_policy_control.cpp
    participant ASSOC as individual_sm_association.cpp
    participant QOS as app_session.cpp
    participant SMF as SMF

    AF->>API: HTTP POST /app-sessions
    Note over AF,API: AppSessionContext with MediaComponent containing qosReference

    API->>PA: post_app_sessions_handler(context, app_session_id, problem_details)

    
    Note over PA,ASSOC: Session Binding - locate N7 SM Policy association
    PA->>EVENT: sm_session_binding(ueIpv4, supi, dnn, association_id, current_decision)
    EVENT->>SMPC: handle_session_binding_request(ipv4, supi, dnn, assoc_id, decision)
    SMPC->>SMPC: m_policy_storage->find_association(ipv4, supi, dnn)
    SMPC->>ASSOC: get_sm_policy_decision_dto()
    ASSOC-->>SMPC: SmPolicyDecision (base N7 decision)
    SMPC-->>PA: association_id and current_decision populated as out params


    Note over PA,QOS: QoS Parameter Processing
    PA->>PA: authorize_service_info(reqData)

    loop For each MediaComponent where qosReferenceIsSet()
        PA->>QOS: handle_qos_requirements(current_decision)
        QOS->>QOS: create_qos_data_from_media_component(current_decision)
        QOS->>QOS: create_qos_characteristics(current_decision)
        QOS->>QOS: setup_qos_monitoring(current_decision)
        QOS-->>PA: handler_result OK
        PA->>PA: qos_flow_processed = true
    end

    PA->>QOS: validate_qos_authorization()
    QOS-->>PA: handler_result OK

    PA->>PA: validate_and_merge_decision(request_decision, current_decision)
    PA->>PA: app_session(reqContext, current_decision, app_session_id)
    PA->>PA: m_app_sessions.insert(app_session_id, app_session)


    Note over PA,SMF: Cross-Service Coordination and SMF Notification
    PA->>EVENT: sm_update_decision(association_id, current_decision)
    EVENT->>SMPC: handle_update_decision_request(association_id, decision)
    SMPC->>ASSOC: set_sm_policy_decision(decision)
    Note over ASSOC: association now stores SmPolicyDecision with qosDecs and pccRules
    SMPC->>SMPC: send_sm_policy_control_update_notify(association)
    SMPC->>SMF: HTTP POST notificationUri/update with smPolicyDecision containing pccRules and qosDecs
    alt 200 OK
        SMF-->>SMPC: 200 OK
    else 4xx or 5xx
        SMF-->>SMPC: error response, propagated to AF
    end


    Note over PA,AF: AF Notification Infrastructure
    PA--xAF: notify_af_qos_status()
    Note over PA,AF: send Npcf_PolicyAuthorization_Notify to AF callback URI on QoS status changes


    Note over SMPC,SMF: Phase 4 - QoS Monitoring Framework
    SMPC--xSMF: configure QosMonitoringData thresholds
    Note over SMPC,SMF: rovision QosMonitoringData to SMF and deliver measurement reports to AF


    PA-->>API: status_code::CREATED
    API-->>AF: 201 Created Location /app-sessions/{app_session_id}
Loading

@tariromukute
tariromukute force-pushed the feat-n5-qos-support branch 3 times, most recently from 29079ab to 858098a Compare August 17, 2026 10:52
@tariromukute
tariromukute marked this pull request as ready for review August 17, 2026 10:53
@tariromukute tariromukute removed do-not-trigger-ci Indicates that CI should not be triggered. help wanted Extra attention is needed labels Aug 17, 2026

@phine-lukas phine-lukas left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a second common-src was added by mistake.

Comment thread src/pcf_app/policy_auth/qos_deriver.cpp
@openairinterface-bot

Copy link
Copy Markdown

CI Build: #22 | Failed on the following stages:

  • Prepare Source Metadata

Git operation failure or internal CI error

@openairinterface-bot

Copy link
Copy Markdown

CI Build: #26 | Failed on the following stages:

  • Prepare Source Metadata

Git operation failure or internal CI error, likely conflicts with develop; please resolve it.

@tariromukute tariromukute added the closed-loop-qos This label is to track the Dynamic Closed Loop QoS Feature of OAI Core Network label Sep 3, 2026
tariromukute and others added 15 commits September 4, 2026 18:32
Classifies SMF Npcf_SMPolicyControl_UpdateNotify responses per TS 29.512
Table 5.7.3-2 (smf_notify_outcome: applied/temporary_rejection/
permanent_rejection/transport_ambiguous) instead of the previous
204-blind, cause-blind handling, and acts on the classification instead
of just logging it:

- SM-side bounded retry-drain queue (retry_drain_queue) for temporary/
  ambiguous outcomes, drained off the now-actually-started task_tick
  heartbeat (task_manager was constructed nowhere before this).
- PA-side pending_rollback_tracker recording what each commit would
  need to undo, populated at apply_with_retry's existing commit point.
- A new SM->PA signal (sm_policy_update_failed) fired only on a
  confirmed permanent rejection, driving a compensating-delta rollback
  through apply_with_retry's own CAS-retry loop, filtered by a per-key
  staleness check (compute_rollback_delta) so a key touched by a later,
  unrelated commit is left alone rather than orphaned.
- notify_af_qos_update_failed: Phase 3 stub, logs only.
- retry-drain/rollback-tracker TTL, cap, and retry/backoff are config-
  tunable (pcf.notify_failure_recovery), not hardcoded.

Fixes two bugs found during implementation, both covered by regression
tests: a stale pre-commit snapshot fed to apply_with_retry's CAS loop
that silently no-op'd the rollback whenever nothing else touched the
association first (added sm_get_association_decision to fetch live
state, extracted perform_compensating_rollback so the "always fetch
fresh" contract is directly testable); and an uncaught nlohmann::json
exception on an empty/non-object SMF error body, found while extracting
the classification logic into its own testable, HTTP-free function
(classify_smf_notify_response).

225 tests passing (21 new: response classification, retry-drain queue,
pending-rollback tracker, rollback delta computation, rollback
orchestration).

Deferred, tracked separately: distinguishing connection-failure from
timeout in http_client (needs a cross-repo oai-cn5g-common-src change);
a 200 response shaped as array<PolicyDecisionFailureCode> is currently
misclassified as success .

Assisted-By: Claude:claude-5-sonnet
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Extracts the CAS-retry loop into policy_auth::apply_decision_with_retry
(sm_update_decision injected, pending_rollback_tracker passed directly
since it's cheap and already tested). apply_with_retry is now a thin
wrapper -- no behavior change for any of its 4 call sites. Also fixes
a stale doc comment (exhaustion returns FORBIDDEN, not
INTERNAL_SERVER_ERROR, since the earlier §11.1 fix).

Adds test coverage notify_failure_recovery_config/builder were
missing, matching their qos_authorization_config/
operator_qos_policy_builder siblings.

236 tests passing (11 new).

Assisted-By: Claude:claude-5-sonnet
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
…osChars tests

getPccRules()/getTraffContDecs()/getQosChars() return maps by value, so
calling them twice (once for .find(), again for .end()) compares an
iterator into an already-destroyed temporary against a different
temporary's end() -- undefined behavior whose outcome depends on
whether the compiler reuses the same stack slot for both temporaries.

validate_and_merge_decision's duplicate PCC-rule-ID and Traffic-
Control-ID rejection checks relied on this by luck; a toolchain change
could silently flip accept/reject. Hoist each map into one local
before comparing, which also drops the redundant per-key map copy.

Same fix applied to the six QosCharacteristics tests exercising the
same pattern, found via the same audit that turned up the
ApplyDecisionWithRetry CI failure (52871a0).

Assisted-By: Claude:claude-5-sonnet
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
…ns for decision updates and rollbacks

Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
…d cause constants for clarity

Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Extract decision_applier (CAS-retry loop) and qos_deriver out of
pcf_policy_authorization/app_session so sm_update_decision, the
pending_rollback_tracker, and QoS policy/reference stores are bound
once at construction instead of threaded per-call. Inject an
http_send_fn seam into pcf_smpc so SMF-notify sends are mockable
without touching the non-virtual http_client submodule type.

Split pcf_smpc's monolithic update-decision handling into
handle_commit_decision_request (CAS + persist only) and
handle_notify_committed_decision_request (re-fetch under lock, notify,
classify, return outcome directly). This removes the prior race where
pending_rollback_tracker recording happened after sm_update_decision
returned while a permanent-rejection signal could already have fired
synchronously inside that same call. Policy Authorization's new
push_decision_change is now the single choke point that commits,
notifies, and triggers compensate_if_pending (renamed from
handle_sm_policy_update_failed) on a permanent rejection -- with no
callback passed into SM and no attempt_id/staging needed.

Also fix pcf_sm_policy_control.hpp pulling in the heavy
http_definitions.hpp (cpr/curl/fmt/nlohmann) for just one type alias;
api-server's CMakeLists.txt has no include path for it, breaking the
PCF_API Docker build target. Swap to the lightweight 3gpp_29.500.h and
forward-declare oai::http::request/response instead.

Assisted-By: Claude:claude-5-sonnet
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
…handling and logging

Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Fold notify_failure_recovery_policy.hpp/_builder.* and
operator_qos_policy.hpp/_builder.* (6 files) into a new
pcf_runtime_policy.{hpp,cpp}: both were single, always-built-together
collaborators for the runtime-policy aggregates pcf_app.cpp builds once
and injects into pcf_smpc/policy_auth_context, so the split forced
pcf_app.cpp, pcf_sm_policy_control.hpp, qos_session_authorization.hpp,
policy_auth_context.hpp, and qos_deriver.hpp to each pull in multiple
headers for what's really one seam.

Fold pending_rollback_tracker.{hpp,cpp}, rollback_delta.{hpp,cpp}, and
rollback_orchestration.{hpp,cpp} into decision_applier.{hpp,cpp}: the
four collaborators (pending_rollback_tracker, decision_applier,
compute_rollback_delta, perform_compensating_rollback) are one
dependency chain with no external consumer of the intermediate links.

Fold app_session_record.hpp into app_session.hpp, qos_types.hpp into
qos_context.hpp, qos_derivation_helpers.hpp into qos_deriver.hpp,
policy_authorization_causes.hpp into
pcf_policy_authorization_status_code.hpp, qos_reference_loader.hpp
into qos_reference_store.hpp, and smf_notify_causes.hpp into
smf_notify_response_classifier.hpp -- each was a single-consumer header
(one struct or a couple of cause constants) whose one caller already
includes a natural host.

Per N5_QoS_Refactor_File_Consolidation_Plan.md: "A header earns its own
file when it has independent consumers or is an independent seam."
Pure move -- no behavior change; only include paths,
CMakeLists.txt's add_library(PCF ...) source list, and test #includes
updated. 18 files removed, 2 added (pcf_runtime_policy.{hpp,cpp}).

Assisted-By: Claude:claude-5-sonnet
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Adds an Unreleased CHANGELOG section for the N5 Policy Authorization
service, QoS derivation, the N7 UpdateNotify path and the new test
infrastructure.

Corrects FEATURE_SET.md: N5 becomes partially supported (app-session
lifecycle works, AF notification does not), the "UpdateNotify feature not
supported" footnote is dropped now that it is implemented, and rows 1 and
9 are updated since AF service information feeds PCC decisions. Adds
:heavy_minus_sign: with a legend for partial support.

Assisted-By: Claude:claude-5-opus
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Comment-only; no functional change. The two reworded warn lines in
rollback_map() are the only observable difference.

Takes the Phase 1/2 TODO blocks from 82 to 30: deletes blocks describing
work that is now done, deletes the commented-out coordination signal
typedefs (recording why they were dropped -- validation runs in-process
and the commit is the coordination point), and collapses the
AF-notification and monitoring blocks to short phase/clause pointers.

Fixes two comments that misdescribed the code: authorize_service_info()
listed QoS authorization as a TODO there when it runs in qos_deriver, and
setup_qos_monitoring() was labelled a mock when it is a no-op.

Rewrites the rollback_map() note, which claimed the per-key staleness skip
avoids orphaning a dependent PCC rule when it can cause one -- and
perform_compensating_rollback()'s derive never calls
validate_policy_decision(), so the inconsistent result is committed and
notified to the SMF. Behaviour unchanged; the fix needs its own tests.

Assisted-By: Claude:claude-5-opus
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
- Add missing #endif in SlicePolicyDecision.h (root cause of the reported error)
- Fix stale oai::model namespace (-> oai::_3gpp::model) across pcf_app
- Stub out unimplemented SFC handling (AfSfcRequirement was removed)
- Fix assorted typos/bugs surfaced along the way (const-void, to_string, etc.)
- Fix stale cmake paths and missing include dir blocking the checks test target

Assisted-By: Claude:claude-5-sonnet
Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
…ription data

Signed-off-by: Tariro Mukute <tariro.mukute@phine.tech>
Explicitly set packetFilterUsage=true in flow_info_from_desc() for all PCF-generated FlowInformation objects.

This ensures the N7 SmPolicyDecision payload explicitly carries packetFilterUsage=true for dynamic N5 (AF) flows, preventing the downstream SMF N1 NAS encoder from dropping the flow filter and omitting the rule in PduSessionModificationCommand

Signed-off-by: Lukas Rotheneder <lukas.rotheneder@phine.tech>
@openairinterface-bot

Copy link
Copy Markdown

CI Build: #29 | Failed on the following stages:

  • Prepare Source Metadata

Git operation failure or internal CI error, likely conflicts with develop; please resolve it.

@sgarg00

sgarg00 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

@tariromukute @phine-lukas please rebase your branch on latest develop and resolve any merge conflicts.
Thanks!!

Signed-off-by: Lukas Rotheneder <lukas.rotheneder@phine.tech>
@openairinterface-bot

Copy link
Copy Markdown

CI Build: #30 | Failed on the following stages:

Signed-off-by: Lukas Rotheneder <lukas.rotheneder@phine.tech>
@openairinterface-bot

Copy link
Copy Markdown

CI Build: #31 | Failed on the following stages:

Signed-off-by: Lukas Rotheneder <lukas.rotheneder@phine.tech>
@openairinterface-bot

Copy link
Copy Markdown

CI Build: #32 | Failed on the following stages:

@phine-lukas phine-lukas added the retrigger-ci Re-run the CI label Sep 7, 2026
@openairinterface-bot

Copy link
Copy Markdown

CI Build: #33 | Failed on the following stages:

@openairinterface-bot

Copy link
Copy Markdown

CI Build: #34 | Failed on the following stages:

Validation: SHA recognized in 99a0222, using "origin/feat-n5-qos-support" as branch name
The following commit(s) are missing a Signed-off-by:

99a0222

Please use 'git commit -s' or 'git commit --signoff' to sign your commits.

For detailed instructions, refer to the CONTRIBUTING file at the root of this repository.

Sanitize ARP priorityLevel in SessionRule and QosData decisions to ensure it
falls within the valid 3GPP TS 38.413 range (1-15). Defaults invalid (0) values
to 1 to prevent SMF NGAP encoding failures.

Signed-off-by: Lukas Rotheneder <lukas.rotheneder@phine.tech>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

closed-loop-qos This label is to track the Dynamic Closed Loop QoS Feature of OAI Core Network imported-from-gitlab Items migrated from GitLab retrigger-ci Re-run the CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants