| English | 中文版 |
Status: Draft Version: 0.7 Date: 2026-09-05 Applies-To: NPS-AaaS-Profile §4.3 — Level 2 Standard Authors: Ori Lynn / INNO LOTUS PTY LTD
This document defines the test cases a deployment MUST disposition before claiming NPS-AaaS-Profile L2 compliance. It covers the current L2-01..L2-08 service contract, plus role-conditional transport, Bridge and HA families. The historical blanket deferral of L2-01..L2-07 was invalid: those requirements already exist in the pre-alpha.20 normative profile. Their frozen alpha.19 disposition is recorded in
aaas-l2-requirement-disposition.json.
impl/dotnet/src/NPS.NWP.Anchor/ running the AnchorNodeClient (CR-0002 §4).NPS-NODE-L2-CERTIFIED.md to the IUT repository
root, fill it in, and sign the attestation with the IUT’s root key.The paired-peer methodology is identical to NPS-Node-L1 §1. The peer requirement upgrades: an L2-passing peer is required wherever L1 used an L1-passing peer.
Self-certification is sufficient at this release. Third-party certification (NPS Cloud CA) is targeted for L3 in 2027 Q1+ and is out of scope here.
| Requirement | Value |
|---|---|
| Network | Loopback only; cases do not require external DNS or inter-host routing |
| Peer | Any L2-passing NPS implementation (reference: NPS.NWP.Anchor + NPS.NDP at NPS v1.0.0-alpha.11 or later) |
| Clock | Wall clock within ±5 s of the peer (ISO 8601 UTC timestamps) |
| File system | Writable directory for the IUT’s key store and (if applicable) topology persistence |
| Wire encoding | Tier-1 JSON MUST be exercised; Tier-2 MsgPack SHOULD be exercised |
| Cluster fixture | The IUT MUST act as the Anchor Node of a single cluster; member nodes are simulated by the peer issuing NDP Announce frames carrying cluster_anchor = IUT’s NID |
| Multi-Anchor fixture | §3.4 (TC-N2-HA-*) only: the peer additionally simulates one or more standby Anchors of the same cluster_anchor cluster, and for TC-N2-HA-07 / -08 the IUT is exercised in its NDP Registry role rather than its Anchor role |
Every case runs in a fresh IUT state: clear the IUT’s topology registry between cases unless the case is explicitly continuation-oriented.
Each case lists the requirement ID from NPS-AaaS-Profile §4.3, the fixture, the actions, and the acceptance criteria.
topology.snapshot / topology.streamThese cases validate L2-08: implementation of the reserved query types defined in NPS-2 §12. All twelve cases MUST pass for L2-08 to be claimed: TC-N2-AnchorTopo-01 through TC-N2-AnchorStream-04 cover happy paths; TC-N2-AnchorTopo-04 through TC-N2-AnchorTopo-08 cover required negative paths (one MUST-reject per error code).
Req: L2-08 (topology.snapshot)
Fixture: IUT acting as Anchor with no prior topology state.
Action:
M1 Memory, M2 Action, M3 Complex) via NDP
Announce frames with cluster_anchor set to the IUT’s NID.QueryFrame with type = "topology.snapshot", topology.scope = "cluster",
default topology.include.
Pass:
CapsFrame carrying anchor_ref = "nps:system:topology:snapshot".cluster_size equals 3.members lists exactly the three NIDs M1, M2, M3 in some order.nid, node_roles, activation_mode.version is a positive integer.anchor_nid.Req: L2-08 (version semantics, NPS-2 §12.3) Fixture: IUT acting as Anchor with no prior topology state. Action:
M1.snapshot_a (records its version: V1).M2.snapshot_b (records its version: V2).
Pass:
V2 > V1 strictly.snapshot_b.cluster_size = snapshot_a.cluster_size + 1.version counter is monotonic across the run.child_anchor and member_countReq: L2-08 (sub-Anchor member representation, NPS-2 §12.1)
Fixture: IUT acting as Anchor; peer-simulated child Anchor CA with 2 of its own members.
Action:
CA to the IUT — node_roles = ["anchor"], cluster_anchor = IUT NID, with metadata indicating CA itself has 2 members.topology.depth = 1.
Pass:
CA carries child_anchor: true.CA carries member_count: 2.truncated field is absent or false (depth 1 is the default and is not exceeded).member_joined on NDP AnnounceReq: L2-08 (topology.stream join event, NPS-2 §12.2)
Fixture: IUT acting as Anchor with no prior topology state.
Action:
SubscribeFrame with type = "topology.stream", topology.scope = "cluster".subscribed ack.M1.
Pass:
DiffFrame with event_type = "member_joined".payload is a full member object whose nid matches M1.seq is greater than the last_seq returned in the subscribed ack.member_left on NDP TTL expiryReq: L2-08 (topology.stream leave event, NPS-2 §12.2)
Fixture: IUT acting as Anchor with one announced member M1 (TTL configured short — e.g. 2 s).
Action:
topology.stream.M1’s announce.TTL + 1 s of the last refresh, the IUT pushes a DiffFrame with event_type = "member_left".payload.nid matches M1’s NID.seq is strictly greater than the most recent member_joined event.topology.since_versionReq: L2-08 (replay semantics, NPS-2 §12.2 / §12.3) Fixture: IUT acting as Anchor with no prior topology state. Action:
topology.stream and announces M1. Peer records the resulting seq = V1.M2 and M3 (yielding V2, V3).topology.since_version = V1.
Pass:
member_joined for M2 and M3, in that order, with seq values V2 and V3.M1 is replayed (its seq = V1 is the boundary; replay starts strictly after).resync_required event is emitted.topology:read) → NWP-TOPOLOGY-UNAUTHORIZEDReq: L2-08 (authorization gate, NPS-2 §12.4; M6) Fixture: IUT acting as Anchor with one announced member. Action:
topology:read in capabilities (all other required capabilities present).QueryFrame with type = "topology.snapshot".
Pass:
ErrorFrame carrying error code NWP-TOPOLOGY-UNAUTHORIZED.NWP-TOPOLOGY-DEPTH-UNSUPPORTEDReq: L2-08 (depth enforcement, NPS-2 §12.1)
Fixture: IUT acting as Anchor, with its maximum topology.depth documented or configurable for the test; use max_depth = 3 if not otherwise specified.
Action:
topology:read) sends a QueryFrame with type = "topology.snapshot" and topology.depth = max_depth + 1 (e.g. 4 for max_depth = 3).
Pass:
ErrorFrame carrying error code NWP-TOPOLOGY-DEPTH-UNSUPPORTED.topology.scope value → NWP-TOPOLOGY-UNSUPPORTED-SCOPEReq: L2-08 (scope validation, NPS-2 §12.1) Fixture: IUT acting as Anchor with one announced member. Action:
topology:read) sends a QueryFrame with type = "topology.snapshot" and topology.scope = "nonexistent_scope".
Pass:
ErrorFrame carrying error code NWP-TOPOLOGY-UNSUPPORTED-SCOPE.topology.filter key → NWP-TOPOLOGY-FILTER-UNSUPPORTEDReq: L2-08 (filter key validation, NPS-2 §12.1) Fixture: IUT acting as Anchor with one announced member. Action:
topology:read) sends a QueryFrame with type = "topology.snapshot" and topology.filter = { "nonexistent_key": "value" }.
Pass:
ErrorFrame carrying error code NWP-TOPOLOGY-FILTER-UNSUPPORTED.type value → NWP-RESERVED-TYPE-UNSUPPORTEDReq: L2-08 (reserved-type validation, NPS-2 §12; M4) Fixture: IUT acting as Anchor. Action:
topology:read) sends a QueryFrame with type = "topology.nonexistent_operation".
Pass:
ErrorFrame carrying error code NWP-RESERVED-TYPE-UNSUPPORTED.NWP-ACTION-NOT-FOUND (these codes are explicitly distinct — see NPS-2 §13).resync_required when version is too oldReq: L2-08 (resync_required semantics, NPS-2 §12.2)
Fixture: IUT acting as Anchor with retention buffer configured to 5 events.
Action:
seq values V1..V10).topology.stream with topology.since_version = 1.
Pass:
event_type = "resync_required" with payload.reason = "version_too_old".resync_required event.topology.snapshot and resubscribing without topology.since_version.These cases apply to an IUT that terminates native-mode NCP-over-TLS at an L2 ingress
(e.g. nps-ingress). They validate the transport-security admission gate added by
NPS-RFC-0006 §6 and NCP v0.8 §7.5.
nps/1.0 negotiated over TLS 1.3Req: NPS-RFC-0006 §6.1–§6.2 Fixture: IUT acting as L2 terminator with a server certificate configured. Action:
nps/1.0 and a valid client certificate.
Pass:
nps/1.0.no_application_protocol (120).Req: NPS-RFC-0006 §6.3 (mTLS)
Fixture: IUT with RequireClientCert = true.
Action:
Req: NPS-RFC-0006 §6.3 Fixture: IUT with a configured trust anchor; peer holds a NIP leaf cert chaining to it. Action:
NCP-NID-MISMATCHReq: NPS-RFC-0006 §6.3, error code NCP-NID-MISMATCH
Fixture: IUT as above.
Action:
urn:nps:agent:A, sends preamble + HelloFrame, and
waits for the backend CapsFrame relayed by the IUT.urn:nps:agent:B.
Pass:
NCP-NID-MISMATCH and does NOT forward the IdentFrame to the backend.These cases apply to an IUT that declares a non-empty bridge_inbound_protocols
(NPS-4 §3.1) — i.e. claims the inbound Bridge conformance profile. The existing
outbound bridge_target round-trip vectors (NPS-2 §16.4)
remain the outbound profile’s suite and are unchanged; NPS-CR-0010 §4 refers to
the two families as TC-N2-BRIDGE-OUT-* / TC-N2-BRIDGE-IN-*, realized here as
the §16.4 vectors and TC-N2-BridgeIn-01..06 respectively.
Req: NPS-2 §16.1.2 MUST-3
Fixture: IUT declaring bridge_inbound_protocols: ["mcp"], fronting one Memory Node and one Action Node.
Action:
initialize, ping,
tools/list, tools/call, resources/list, resources/read.
Pass:
tools/list surfaces the Action Node’s actions with qualified node__action names (CR-0010 §5.1).resources/list / resources/read project the Memory Node’s read surface; an IUT fronting no
Memory Node MUST still serve both methods (empty set), not return “method not found”.Req: NPS-2 §16.1.2 MUST-2, MUST-3
Fixture: IUT declaring bridge_inbound_protocols: ["grpc"], fronting one Action Node; client uses the published nwp_ingress.proto contract.
Action:
Req: NPS-2 §16.1.2 MUST-2, MUST-3
Fixture: IUT declaring bridge_inbound_protocols: ["a2a"], fronting one Action Node.
Action:
tasks/send naming a listed skill.
Pass:
tasks/send completes and returns the action result as the task artifact.Req: NPS-CR-0010 §5.1 (forgiving input)
Fixture: IUT fronting two nodes where exactly one defines action orders_lookup and both define action status.
Action:
tools/call (MCP) with bare name orders_lookup.tools/call with bare name status.
Pass:
Req: NPS-2 §16.1.2 MUST-4, §16.3
Fixture: IUT declaring bridge_inbound_protocols: ["mcp"], fronting an Action Node that can be made to fail.
Action:
isError: true for auth-class errors.Req: NPS-2 §16.1.2 MUST-5, error code NWP-BRIDGE-DIRECTION-UNSUPPORTED
Fixture: IUT declaring bridge_inbound_protocols: ["mcp"] only.
Action:
tasks/send to the IUT’s inbound surface.
Pass:
NWP-BRIDGE-DIRECTION-UNSUPPORTED.hint SHOULD carry both declared arrays (bridge_protocols,
bridge_inbound_protocols).These cases realize the TC-N2-HA-* family promised by NPS-CR-0009 §4.
They validate the cluster-ownership contract that CR adds: the cluster_epoch fence
(NPS-2 §12.2), the two finalised anchor_state sub-types
anchor_failover / anchor_quorum_lost, and the highest-epoch resolution rule
(NPS-4 §9). Multi-Anchor HA operation is an AaaS Profile L3
requirement; an L1/L2 single-Anchor cluster remains fully conformant and is covered by
TC-N2-HA-09 instead.
The contract spans two node roles, so §4 certifies the family as three groups:
TC-N2-HA-01..06 exercise the IUT as an Anchor; TC-N2-HA-07..08 exercise it as an
NDP Registry; TC-N2-HA-09 is the single-Anchor mirror of the first group and is
mutually exclusive with it.
Fixture conventions for this sub-section: C is the cluster’s stable cluster_anchor NID;
A1 / A2 are Anchors of that cluster; E is the cluster_epoch the active owner
currently holds. How ownership is acquired, leased, and relinquished is
implementation-defined — CR-0009 §1 deliberately does not specify the consensus transport —
so each case names only the observable trigger, and the IUT MUST document the control
surface used to drive it.
Underspecified in CR-0009, pinned here for testability: (a) an inbound frame carrying a lower
cluster_epochthan the receiver’s own is not covered by CR-0009 §3.1; these cases assert nothing about it, and implementations MAY accept or reject it until a follow-up CR settles the point. (b) CR-0009 §3.3 says quorum recovery is signalled by “a normalanchor_stateevent with a freshcluster_epoch” without naming a sub-type, so TC-N2-HA-04 asserts the fresh epoch only, not a particularfieldvalue.
cluster_epoch carried on every topology read surfaceReq: NPS-CR-0009 §3.1 (ownership fence), NPS-2 §12.2
Fixture: IUT acting as the active Anchor of cluster C at cluster_epoch = E; peer simulates standby A2; one announced member.
Action:
topology:read) sends a QueryFrame with type = "topology.snapshot".SubscribeFrame with type = "topology.stream" and reads the subscribed ack.
Pass:
cluster_epoch as a uint64 ≥ 1.subscribed ack reports the same cluster_epoch as the snapshot.C; the IUT does not report a different epoch on the two surfaces.anchor_failover wire shape on planned handoverReq: NPS-CR-0009 §3.2, NPS-2 §12.2 anchor_state sub-type anchor_failover
Fixture: IUT acting as the active Anchor of C at cluster_epoch = E; peer-simulated standby A2 ready to take ownership.
Action:
topology.stream and waits for the subscribed ack.A2 through the IUT’s documented control surface.
Pass:
DiffFrame with event_type = "anchor_state" and payload.field = "anchor_failover".payload.details carries all three required fields successor_nid, cluster_epoch, reason.successor_nid equals A2’s NID; cluster_epoch is a uint64 strictly greater than E; reason = "planned".member_updated or resync_required — the anchor_state envelope with the field discriminator is required.anchor_failover on active loss is terminalReq: NPS-CR-0009 §3.2 (fenced prior leader sends a terminal anchor_failover, then closes its streams)
Fixture: IUT acting as the active Anchor of C at cluster_epoch = E with one attached subscriber; peer-simulated A2 will claim ownership at E + 1 once the IUT’s lease lapses.
Action:
topology.stream.A2 claims ownership at E + 1 and the fact is made observable to the IUT.
Pass:
anchor_state event with payload.field = "anchor_failover", payload.details.reason = "active_lost", successor_nid = A2’s NID, and cluster_epoch = E + 1.anchor_quorum_lost wire shape and degraded read-only operationReq: NPS-CR-0009 §3.3, NPS-2 §12.2 anchor_state sub-type anchor_quorum_lost
Fixture: IUT acting as the active Anchor of a three-Anchor cluster with quorum_size = 2; one announced member; one attached subscriber.
Action:
topology.stream.available = 1, below quorum_size = 2).topology.snapshot (read) and, separately, a topology-mutating write.DiffFrame with event_type = "anchor_state" and payload.field = "anchor_quorum_lost".payload.details carries both required fields quorum_size and available as uint32, with quorum_size = 2 and available = 1 matching the fixture.AnnounceFrame reports health: "degraded" (NPS-4 §3.2).NWP-ANCHOR-NOT-LEADER.anchor_state event whose cluster_epoch is strictly greater than the pre-loss epoch; it does NOT resume accepting writes at the pre-loss epoch.NWP-ANCHOR-NOT-LEADERReq: NPS-CR-0009 §3.1, error code NWP-ANCHOR-NOT-LEADER
Fixture: IUT configured as a standby Anchor of C; the peer-simulated Anchor A1 holds ownership at cluster_epoch = E.
Action:
topology:read and write authority) sends the IUT a topology-mutating push — a DiffFrame / AnnounceFrame that would change cluster membership — carrying cluster_epoch = E.topology.snapshot.
Pass:
ErrorFrame carrying NWP-ANCHOR-NOT-LEADER, which maps to NPS-CLIENT-CONFLICT (HTTP 409 in HTTP mode).A1 and report success — a standby MUST reject, not proxy.cluster_epoch (≤ E). A standby MUST NOT report an epoch it has never observed.NWP-ANCHOR-EPOCH-FENCEDReq: NPS-CR-0009 §3.1, error code NWP-ANCHOR-EPOCH-FENCED
Fixture: IUT acting as the active Anchor of C, believing itself to hold cluster_epoch = E.
Action:
cluster_epoch = E + 1 — i.e. it comes from a peer that has already taken ownership under a higher epoch.cluster_epoch = E.
Pass:
ErrorFrame carrying NWP-ANCHOR-EPOCH-FENCED (→ NPS-CLIENT-CONFLICT).NWP-ANCHOR-NOT-LEADER — the codes are distinct: NOT-LEADER means “you wrote to a non-owner”, EPOCH-FENCED means “the receiver is a superseded owner”.cluster_epoch fences the receiver.cluster_epoch AnchorReq: NPS-CR-0009 §3.4, NPS-4 §9 multi-Anchor cluster resolution
Fixture: IUT acting as an NDP Registry (reference: nps-registry) with no prior entries.
Action:
A1 with cluster_anchor = C and cluster_epoch = 4.A2 with cluster_anchor = C and cluster_epoch = 7; both entries are live and within TTL.ResolveFrame for C.A1 at cluster_epoch = 4 (a stale leader still beating) and resolves C again.A3 for a second cluster C2 omitting cluster_epoch, alongside an A4 for C2 at cluster_epoch = 2, and resolves C2.
Pass:
A2 — the live entry with the highest cluster_epoch.cluster_epoch = 7.A2: the Registry MUST NOT downgrade cluster C to epoch 4 (monotonic per cluster).A4: an announcement omitting cluster_epoch is treated as epoch 1 and therefore loses to any explicit epoch greater than 1.NDP-CLUSTER-SPLITReq: NPS-CR-0009 §3.4, error code NDP-CLUSTER-SPLIT
Fixture: IUT acting as an NDP Registry with no prior entries.
Action:
A1 with cluster_anchor = C and cluster_epoch = 5.A2 with cluster_anchor = C and cluster_epoch = 5; both entries are live and within TTL.ResolveFrame for C.A1’s entry age past its TTL and resolves C again.
Pass:
ErrorFrame carrying NDP-CLUSTER-SPLIT (→ NPS-CLIENT-CONFLICT).C.A2 and succeeds: the split clears without operator intervention once only one live entry remains (or one side is superseded by a higher epoch).cluster_epoch = 1 and emits no HA eventsReq: NPS-CR-0009 §4 / §5 (backward compatibility), NPS-2 §12.2 Fixture: IUT acting as the only Anchor of its cluster with no multi-Anchor HA configured — the default L1/L2 deployment shape. Action:
topology.stream and takes a topology.snapshot.cluster_epoch entirely, as a pre-alpha.17 peer would.subscribed ack in steps 1 and 4 either omits cluster_epoch or reports cluster_epoch = 1; the value never increases, including across the restart.anchor_state event with payload.field = "anchor_failover" or "anchor_quorum_lost" is pushed at any point in the run.NWP-ANCHOR-NOT-LEADER or NWP-ANCHOR-EPOCH-FENCED during the run.These cases close the former follow-up-only gap. They exercise the assembled AaaS
service boundary, not merely an SDK codec. L2-01..L2-05 are mandatory and cannot be
na. L2-06/07 are SHOULD requirements: a deployment that deliberately does not
implement one MAY record na, but the case result MUST carry a non-empty message
explaining the exception. Component evidence and gaps are indexed in
aaas-l2-requirement-disposition.json.
Req: L2-01 (MUST), NPS-5 §3.1 Fixture: IUT exposes one Action whose implementation requires two ordered internal worker operations; the harness captures the IUT-to-orchestrator boundary. Action: Send a valid ActionFrame and allow the service to start internal orchestration. Pass:
Req: L2-02 (MUST), NPS-5 §3.1.2
Fixture: Same service boundary as TC-N2-AaaS-01 with TaskFrame capture enabled.
Action: Invoke once with a valid W3C traceparent, then once without one.
Pass:
trace_id and carries a fresh 16-hex span_id plus the inbound trace flags.trace_id and span_id values.Req: L2-03 (MUST), token-budget §2.1–§2.4 Fixture: IUT Action returns a deterministic logical payload and accepts a CGN-Estimate budget. Action: Invoke with sufficient and insufficient budgets using JSON and one other supported encoding. Pass:
token_est and identifies the estimate surface rather than CGN-Billing.Req: L2-04 (MUST), NPS-5 §4
Fixture: A two-worker TaskFrame has preflight=true; the harness can make either worker available or unavailable.
Action: Run once with one unavailable worker and once with both available.
Pass:
NOP-RESOURCE-INSUFFICIENT and dispatches no task node.(agent, action) pair before the first task dispatch.Req: L2-05 (MUST), NPS-5 §3.5.2 Fixture: A worker can return retryable/non-retryable failures or exceed node/overall deadlines. Action: Exercise retry success, retry exhaustion, a non-retryable error, node timeout and overall timeout. Pass:
max_retries, with the same logical subtask and idempotency identity.NOP-DELEGATE-TIMEOUT and NOP-TASK-TIMEOUT, with no late result changing the terminal state.Req: L2-06 (SHOULD), NPS-2 §7.2–§7.3
Fixture: IUT advertises an Action and has a queryable task store.
Action: Submit ActionFrame.async=true, poll system.task.status, and cancel a separate running task.
Pass:
task_id, poll_url and request_id.cancelled; terminal and unknown-task negative paths return the specified errors.Req: L2-07 (SHOULD), NPS-5 §3.4.1–§3.4.2 Fixture: Worker and orchestrator exchange a multi-frame AlignStream through a controllable CGN window. Action: Exhaust the window, restore it with a reverse frame, ACK consumed sequence numbers, then NAK a retained and an evicted sequence. Pass:
ack_seq advances the retained window; a resolvable nak_seq replays from the requested sequence without reordering.NOP-STREAM-NAK-UNRESOLVABLE; no frame is silently skipped.A conformance run produces a manifest (JSON) summarizing per-case outcomes. The
manifest is embedded into NPS-NODE-L2-CERTIFIED.md:
{
"profile": "NPS-Node-L2",
"profile_version": "0.7",
"scope": ["L2-01", "L2-02", "L2-03", "L2-04", "L2-05", "L2-08"],
"iut": {
"name": "example-anchor",
"version": "0.1.0",
"nid": "urn:nps:node:example.com:anchor-01"
},
"peer": {
"name": "nps-dotnet-reference",
"version": "1.0.0-alpha.11"
},
"run": {
"date": "2026-04-27T00:00:00Z",
"environment": "linux-x64 / 1 vCPU / 1 GB"
},
"cases": [
{ "id": "TC-N2-AaaS-01", "result": "pass" },
{ "id": "TC-N2-AaaS-02", "result": "pass" },
{ "id": "TC-N2-AaaS-03", "result": "pass" },
{ "id": "TC-N2-AaaS-04", "result": "pass" },
{ "id": "TC-N2-AaaS-05", "result": "pass" },
{ "id": "TC-N2-AaaS-06", "result": "na", "message": "Documented SHOULD exception: this service exposes synchronous Actions only." },
{ "id": "TC-N2-AaaS-07", "result": "na", "message": "Documented SHOULD exception: this service emits final results only and does not advertise AlignStream." },
{ "id": "TC-N2-AnchorTopo-01", "result": "pass" },
{ "id": "TC-N2-AnchorTopo-02", "result": "pass" },
{ "id": "TC-N2-AnchorTopo-03", "result": "pass" },
{ "id": "TC-N2-AnchorTopo-04", "result": "pass" },
{ "id": "TC-N2-AnchorTopo-05", "result": "pass" },
{ "id": "TC-N2-AnchorTopo-06", "result": "pass" },
{ "id": "TC-N2-AnchorTopo-07", "result": "pass" },
{ "id": "TC-N2-AnchorTopo-08", "result": "pass" },
{ "id": "TC-N2-AnchorStream-01", "result": "pass" },
{ "id": "TC-N2-AnchorStream-02", "result": "pass" },
{ "id": "TC-N2-AnchorStream-03", "result": "pass" },
{ "id": "TC-N2-AnchorStream-04", "result": "pass" },
{ "id": "TC-N2-Tls-01", "result": "pass" },
{ "id": "TC-N2-Tls-02", "result": "pass" },
{ "id": "TC-N2-Tls-03", "result": "pass" },
{ "id": "TC-N2-Tls-04", "result": "pass" },
{ "id": "TC-N2-BridgeIn-01", "result": "na" },
{ "id": "TC-N2-BridgeIn-02", "result": "na" },
{ "id": "TC-N2-BridgeIn-03", "result": "na" },
{ "id": "TC-N2-BridgeIn-04", "result": "na" },
{ "id": "TC-N2-BridgeIn-05", "result": "na" },
{ "id": "TC-N2-BridgeIn-06", "result": "na" },
{ "id": "TC-N2-HA-01", "result": "na" },
{ "id": "TC-N2-HA-02", "result": "na" },
{ "id": "TC-N2-HA-03", "result": "na" },
{ "id": "TC-N2-HA-04", "result": "na" },
{ "id": "TC-N2-HA-05", "result": "na" },
{ "id": "TC-N2-HA-06", "result": "na" },
{ "id": "TC-N2-HA-07", "result": "na" },
{ "id": "TC-N2-HA-08", "result": "na" },
{ "id": "TC-N2-HA-09", "result": "pass" }
],
"summary": { "pass": 22, "fail": 0, "skip": 0, "na": 16 }
}
The example above is a single-Anchor AaaS service: it passes the five mandatory
L2-01..L2-05 cases, records explicit SHOULD exceptions for L2-06/07, passes the 12
topology and 4 TLS cases, is not a Bridge, does not run a Registry, and declares no
multi-Anchor HA. TC-N2-HA-01..08 are therefore na, while TC-N2-HA-09 passes.
Certification is granted per case family, all-or-nothing within the family:
TC-N2-AaaS-01..05) — all five MUST pass for a
full AaaS L2 claim; na, skip, and component-only evidence are insufficient.TC-N2-AaaS-06..07) — each SHOULD pass. na is
accepted only with a non-empty case message documenting the implementation’s
reasoned exception.TC-N2-AnchorTopo-*, TC-N2-AnchorStream-*) — all 12 MUST pass
for any L2 claim. There are no optional cases; an Anchor Node either implements
topology.snapshot and topology.stream per NPS-2 §12 or it does not.TC-N2-Tls-*) — all 4 MUST pass for an IUT that terminates
native-mode NCP-over-TLS; na otherwise.TC-N2-BridgeIn-*) — all 6 MUST pass for an IUT declaring a
non-empty bridge_inbound_protocols; na for outbound-only or non-Bridge IUTs.TC-N2-HA-01..TC-N2-HA-06) — all 6 MUST pass for an
IUT that declares multi-Anchor HA (an AaaS Profile L3 capability, NPS-CR-0009 §4);
na for a single-Anchor IUT.TC-N2-HA-07, TC-N2-HA-08) — both MUST pass for an
IUT that implements the NDP Registry resolve surface; na for an Anchor-only IUT.TC-N2-HA-09) — MUST pass for a single-Anchor
IUT; na for an IUT that declares multi-Anchor HA. Exactly one of this case and the
Anchor-side family is na in a valid manifest — they are mutually exclusive, never both
na and never both pass.A family marked na in full does not block certification of the others; a family
partially na is a manifest error.
The seven AaaS cases are part of the current pre-alpha.20 contract. They MUST NOT
be removed from scope or treated as a future-CR exemption.
| Language | Path | Status |
|---|---|---|
| .NET 10 (xUnit), Anchor topology | impl/dotnet/tests/NPS.Tests/Nwp/Anchor/AnchorTopologyTests.cs |
Implemented |
| .NET 10 (xUnit), NCP-over-TLS ingress | tools/daemons/nps-ingress/tests/IngressTlsConformanceTests.cs |
Implemented; TC-N2-Tls-01..04 |
| Python | impl/python/tests/conformance/node_l2/ |
TODO (Phase 2) |
| TypeScript | impl/typescript/tests/conformance/node-l2/ |
TODO (Phase 2) |
The reference suite’s test names MUST align with the TC-N2-* IDs above so a
test-run report maps 1:1 onto the §4 manifest.
| Version | Date | Changes |
|---|---|---|
| 0.7 | 2026-09-05 | Added stable TC-N2-AaaS-01..07 cases for current AaaS L2-01..L2-07 instead of deferring them wholesale. L2-01..05 are mandatory; L2-06/07 permit only reasoned SHOULD exceptions. Expanded the manifest to 38 cases and linked the machine-readable alpha.19 disposition. |
| 0.6 | 2026-08-31 | Corrected TC-N2-Tls-03..04 to exercise the normative interactive NPS-1 §2.6 sequence: preamble + Hello, relayed backend Caps, then Ident. This prevents a pipelined-only test from missing implementations that deadlock or bypass certificate/Ident binding on the normal post-Caps path. |
| 0.5 | 2026-08-01 | New §3.4 Multi-Anchor High Availability (TC-N2-HA-01..09) realizing the TC-N2-HA-* family promised by NPS-CR-0009 §4: cluster_epoch present on both topology read surfaces, anchor_failover wire shape on planned handover and on active loss (terminal event then stream close), anchor_quorum_lost wire shape plus degraded read-only operation and recovery at a fresh epoch, NWP-ANCHOR-NOT-LEADER on a standby write, NWP-ANCHOR-EPOCH-FENCED on a superseded leader, NDP highest-epoch resolution with no downgrade, equal-epoch split-brain → NDP-CLUSTER-SPLIT, and single-Anchor backward compatibility at cluster_epoch = 1. §2 gains a multi-Anchor fixture row; §4 manifest now enumerates four case families (12 topology + 4 TLS + 6 bridge + 9 HA) and defines the mutually exclusive HA-Anchor-side / single-Anchor-backward-compat pairing. Two points left open by CR-0009 (lower-epoch inbound frames; the anchor_state sub-type used to signal quorum recovery) are called out in §3.4 rather than asserted. |
| 0.4 | 2026-07-23 | New §3.3 Bridge Node Inbound (TC-N2-BridgeIn-01..06) realizing the TC-N2-BRIDGE-IN-* family promised by NPS-CR-0010 §4: full MCP method set incl. resources/*, gRPC + A2A inbound round-trips, bare-vs-qualified name resolution, §16.3 error-mapping fidelity, and NWP-BRIDGE-DIRECTION-UNSUPPORTED refusal. §4 manifest now enumerates all three case families (12 topology + 4 TLS + 6 bridge) with per-family certification and na semantics. Also retro-adds the missing 0.3 changelog row. |
| 0.3 | 2026-06-12 | (Retro-added row — change shipped in alpha.13 without a changelog entry.) New §3.2 NCP-over-TLS Ingress (TC-N2-Tls-01..04) validating the NPS-RFC-0006 §6 admission gate: ALPN nps/1.0, mTLS requirement, trust-anchor validation + session NID binding, and NCP-NID-MISMATCH on IdentFrame/certificate mismatch. |
| 0.2 | 2026-05-01 | Added 5 negative-path test cases (TC-N2-AnchorTopo-04 through -08) to enforce the “every MUST-reject clause has a failure-path TC” standard: unauthorized access (M6 capability gate, NWP-TOPOLOGY-UNAUTHORIZED), depth cap exceeded (NWP-TOPOLOGY-DEPTH-UNSUPPORTED), unrecognized scope (NWP-TOPOLOGY-UNSUPPORTED-SCOPE), unrecognized filter key (NWP-TOPOLOGY-FILTER-UNSUPPORTED), unrecognized reserved type (NWP-RESERVED-TYPE-UNSUPPORTED). Total cases: 7 → 12. Fixed node_kind → node_roles in TC-N2-AnchorTopo-01 and -03 (M1 consistency). |
| 0.1 | 2026-04-27 | Initial draft: 7 test cases covering L2-08 (topology.snapshot / topology.stream) per NPS-CR-0002. Paired-peer methodology inherited from L1. |
Copyright: LabAcacia / INNO LOTUS PTY LTD · Apache 2.0