OID index ranking

Which module owns an OID that more than one of them defines. gen_index() records every module that defines a given OID; this is the projection of that fact a consumer resolving an OID to one module to load needs.

Deciding which module owns an OID, and rendering that as an index.

gen_index() records the complete fact: every module that defines a given OID, all of them. A consumer that resolves an OID to one module to load – splunk-connect-for-snmp does exactly this, on every trap – needs a winner instead, and a winner is a lossy projection of that fact. This module computes the projection; gen_index() keeps recording the fact.

The rule ranks a module by five terms, best first:

  1. Live before obsolete. A module whose every OBJECT-TYPE and NOTIFICATION-TYPE is obsolete loses to one that still defines something.

  2. Tier. Standard, then Internet-Draft, then vendor. This is the one term that is not a property of the MIB text: it is declared per source namespace by the build’s manifest, because “standard” is a statement about where a module comes from – see pysmi.corpus.namespace.

  3. Owner before mention. A MODULE-IDENTITY on an arc outranks an OBJECT-IDENTITY on that same arc. CLAB-DEF-MIB registers clabTopoMib so its siblings can hang objects off it, but CLAB-TOPO-MIB is the module that arc belongs to.

  4. Newest revision. The latest MODULE-IDENTITY REVISION, read through normalise_revision – the same check the compiler applies when it chooses between two copies of one module, so a stamp that is not a date cannot win here either.

  5. RFC number, then module name. The later RFC wins, which settles the SMI root arcs: RFC1065-SMI, RFC1155-SMI and SNMPv2-SMI define them identically, none of the three carries a MODULE-IDENTITY to compare, and without this term the winner is whichever name sorts first – which is how RFC1065-SMI came to own ten arcs that RFC 2578 defines. Supersession cannot decide it, since RFC 1155 is STD 16 and still an Internet Standard; publication order can. A module no RFC publishes ranks below every module one does, and the module name is the final tie-break, so the rule is total.

Adapted from index.py in pysnmp/mibs, which is where this rule was worked out; the difference is that the tier arrives declared rather than inferred from a directory layout, and that revisions go through normalise_revision. See pysnmp/pysmi#182 and pysnmp/mibs#352.

pysmi.corpus.index.ANCHOR_RANK: Final[dict[str, int]] = {'moduleidentity': 0, 'objectidentity': 1}

Classes that make a module the owner of an arc rather than a mention of it, and how they rank against each other.

pysmi.corpus.index.ANCHOR_RANK_FALLBACK: Final[int] = 2

Rank given to an OID taken from a module that has no anchor at all, whose OIDs are read off its ordinary definitions instead.

pysmi.corpus.index.OBJECT_CLASSES: Final[tuple[str, ...]] = ('objecttype', 'notificationtype')

Classes whose status says whether a module still defines anything current.

pysmi.corpus.index.arcs(oid: str) tuple[int, ...]

Sort key placing an OID after every prefix of it, numerically.

Parameters:

oid – dotted-decimal OID

Returns:

Its arcs as integers, or an empty tuple for anything that is not one.

pysmi.corpus.index.merge_frozen(frozen: Iterable[tuple[str, str]], ranked: dict[str, str], modules: Iterable[str]) tuple[dict[str, str], int, int]

Replay a frozen index, dropping what is gone and adding what is new.

The legacy index answers consumers that key on the module name a given OID resolves to, so the one thing it must not do is change an answer it has already given – including the answers now known to be wrong, which is what makes it a freeze rather than a stale copy. Everything else is fair game:

  • a row whose module is no longer compiled is dropped, because it names a module the corpus cannot serve;

  • an OID the snapshot never carried is added from the ranked index, because a caller that had no answer for it cannot be broken by getting one.

Without the second rule a module added to the corpus would never reach the legacy index, and a consumer that has not moved to the ranked one would keep resolving nothing for it.

Parameters:
  • frozen – the snapshot, as read_index() yields it

  • ranked – the ranked index, for OIDs the snapshot does not carry

  • modules – the modules this build compiled

Returns:

The merged index, how many rows were dropped, and how many were added.

pysmi.corpus.index.module_rank(module: str, document: dict[str, Any], *, tier: int = 0, rfc: int = 0) tuple[tuple[Any, ...], tuple[Any, ...]]

How a module ranks for an OID it defines, lower winning.

Returned in two halves because the anchor term sits between them: how strongly a module claims an arc is a property of the OID rather than of the module, and oids_of() reports it per OID.

Parameters:
  • module – module name

  • document – the module as JsonCodeGen emits it

Keyword Arguments:
Returns:

The terms ranking above the anchor, and those ranking below it, with the module name last so that the rule is total. Both revision and RFC number are negated, so that the later of each sorts first in a rule where lower wins throughout.

pysmi.corpus.index.newest_revision(document: dict[str, Any]) str

The latest MODULE-IDENTITY revision in a jsondoc, as a sortable stamp.

Every revision goes through read_stamp(), so a stamp that is not a date is dropped rather than ranked. HPR-MIB’s 970514000000Z – year 9705, month 14 – is the case that matters: reducing it to digits and comparing the number, which is what the index implementation in pysnmp/mibs does, makes it sort above every date there will ever be, and the module carrying it wins every collision it takes part in, for good.

LAST-UPDATED counts alongside the REVISION clauses. It is a revision by any reading, and a module carrying one without any REVISION clause would otherwise rank as though it had never been dated.

Parameters:

document – a module as JsonCodeGen emits it

Returns:

The newest readable revision as YYYYMMDDHHMMZ, or an empty string when the module carries none that can be placed.

pysmi.corpus.index.oids_of(document: dict[str, Any]) list[tuple[str, int]]

Every OID a module claims, with how strongly it claims it.

A module’s anchors – its MODULE-IDENTITY and any OBJECT-IDENTITY – are what it registers arcs with, so those are what the index carries. A module with no anchor at all is read off its ordinary definitions instead, at a rank below every anchor, so that a TEXTUAL-CONVENTIONS-only module cannot take an arc from the module that registered it.

Parameters:

document – a module as JsonCodeGen emits it

Returns:

(oid, anchor rank) pairs, in document order.

pysmi.corpus.index.rank_index(documents: Iterable[tuple[str, dict[str, Any], int, int]]) dict[str, str]

Resolve every OID the given modules define to the one that owns it.

Parameters:

documents(module, jsondoc, tier, rfc) for each module in the corpus. Order does not affect the result – that is the point of the rule.

Returns:

OID to module name, for every OID any of them defines.

pysmi.corpus.index.read_documents(directory: str, tiers: dict[str, int], rfcs: dict[str, int], modules: Iterable[str] | None = None) Iterator[tuple[str, dict[str, Any], int, int]]

Read a directory of jsondoc files as rank_index() takes them.

Parameters:
  • directory – where JsonCodeGen output was written

  • tiers – module name to tier rank, for modules whose namespace declared one. A module not named here ranks as tier 0, which is what a corpus of standard modules alone should be.

  • rfcs – module name to publishing RFC number

  • modules – the modules to read, where the caller knows which ones this build produced. Everything in the directory otherwise – which makes an index of a tree left there by an earlier build with a different input set, naming modules this corpus does not carry.

Yields:

(module, jsondoc, tier, rfc) per file, in sorted file order.

pysmi.corpus.index.read_index(text: str) Iterator[tuple[str, str]]

Read a published index back, as (module, oid) pairs.

Rows that are not two fields are skipped: the file is a published artifact that has been edited by hand, and one malformed row should not cost the rest of it.

Parameters:

text – the CSV as render_index() writes it

Yields:

(module, oid) for each usable row.

pysmi.corpus.index.read_stamp(stamp: str) str | None

Read a revision as written in a jsondoc, as a sortable stamp.

Two spellings reach here. JsonCodeGen renders a revision as YYYY-MM-DD HH:MM, having already made an ExtUTCTime of it; a caller ranking modules from some other source may hand over the raw YYMMDDHHMMZ or YYYYMMDDHHMMZ the MIB carries. Both reduce to digits and are then read as a date by normalise_revision, which is what refuses the ones that are not dates.

Parameters:

stamp – the revision as written

Returns:

YYYYMMDDHHMMZ, or None for anything that does not denote a date.

pysmi.corpus.index.render_index(index: dict[str, str]) str

Render an OID-to-module mapping as the published CSV.

MODULE,OID per row, ordered by OID numerically, which is the shape splunk-connect-for-snmp parses into its mib_map.

Parameters:

index – OID to module name

Returns:

The CSV text, newline-terminated.

pysmi.corpus.index.revision_rank(document: dict[str, Any]) int

The newest readable revision as a number that sorts newest first.

Parameters:

document – a module as JsonCodeGen emits it

Returns:

The negated stamp, so that a later date sorts lower, and 0 for a module carrying no revision that can be placed – which sorts after every module that does.

Precedence vectors

The ranking rule above, and the one compile() applies to a module found in several sources, published as data so that pysnmp’s three copies of it cannot drift from these. See Corpus database schema.

The precedence rule, published as data so that a second copy cannot drift.

“Newest MODULE-IDENTITY revision wins, configured order breaks ties only” is implemented three times, in two repositories that cannot import each other. pysmi ranks a module found in several sources (rank_by_revision()) and ranks modules anchored at the same OID (rank_index()). pysnmp ranks the same module found in several MIB directories, and again when several corpora carry it. pysmi is an optional dependency of pysnmp, so pysnmp cannot import the rule at runtime; pysmi cannot import pysnmp at all.

That leaves the agreement unchecked in both directions, and the disagreement does not surface where it is caused. It surfaces when a trap decodes against the wrong definition of a module, long after whichever side moved.

This module closes it the way pysmi.corpus.conformance closes the reader contract: VECTORS states the decisions, pysnmp asserts its own implementations against the same file in its own CI, and run_vectors() asserts pysmi’s here. A change on either side that would break the other fails in whichever project moved.

The vectors are plain data on purpose – a reader in another language runs them by reading this module’s JSON rendering rather than by importing it. Revisions are given in the normalised YYYYMMDDHHMMZ form that comparison actually happens in, except in the normalise_revision vectors, which are what pins that normalisation itself.

pysmi.corpus.precedence.CONTESTED: Final = '1.3.6.1.4.1.99999.1'

The OID every oid_precedence vector contends over.

One arc, claimed by every candidate, so that nothing but the ranking rule can decide it.

pysmi.corpus.precedence.ELSEWHERE: Final = '1.3.6.1.4.1.99999'

Where a module that does not anchor CONTESTED anchors instead.

pysmi.corpus.precedence.VECTORS: Final[tuple[dict[str, Any], ...]] = ({'expect': '202401010000Z', 'id': 'revision-wide-form-is-unchanged', 'op': 'normalise_revision', 'stamp': '202401010000Z', 'why': 'The form RFC 2578 prefers is already what comparison wants.'}, {'expect': '200210160000Z', 'id': 'revision-short-form-below-70-is-2000s', 'op': 'normalise_revision', 'stamp': '0210160000Z', 'why': 'RFC 2578 Section 2: two-digit years under 70 are 2000s.'}, {'expect': '199908190000Z', 'id': 'revision-short-form-from-70-is-1900s', 'op': 'normalise_revision', 'stamp': '9908190000Z', 'why': 'The pivot. Widened the other way, a 1999 revision sorts above a 2002 one and the older copy wins for good.'}, {'expect': None, 'id': 'revision-that-is-not-a-date-is-refused', 'op': 'normalise_revision', 'stamp': '970514000000Z', 'why': 'HPR-MIB ships LAST-UPDATED 970514000000Z -- thirteen characters, read wide as year 9705 month 14. Ranked rather than refused it sorts above every date there will ever be.'}, {'expect': None, 'id': 'revision-of-the-wrong-length-is-refused', 'op': 'normalise_revision', 'stamp': '2024010100Z', 'why': 'Neither of the two forms RFC 2578 admits.'}, {'candidates': [{'module': 'EXAMPLE-MIB', 'revision': '202001010000Z'}, {'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}], 'expect': {'order': [1, 0], 'rule': 'newest MODULE-IDENTITY revision', 'winner': 1}, 'id': 'module-newest-revision-beats-configured-order', 'op': 'module_precedence', 'why': 'The whole rule in one case: the copy configured second wins because it is newer.'}, {'candidates': [{'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}, {'module': 'EXAMPLE-MIB', 'revision': '202001010000Z'}], 'expect': {'order': [0, 1], 'rule': 'newest MODULE-IDENTITY revision', 'winner': 0}, 'id': 'module-newest-revision-when-already-first', 'op': 'module_precedence', 'why': 'The rule has to be applied, not skipped, when it agrees with configured order -- otherwise the reported rule is wrong even though the winner is right.'}, {'candidates': [{'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}, {'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}], 'expect': {'order': [0, 1], 'rule': 'source order; equal MODULE-IDENTITY revisions', 'winner': 0}, 'id': 'module-equal-revisions-fall-to-configured-order', 'op': 'module_precedence', 'why': "Nothing to choose between them, so the caller's order stands."}, {'candidates': [{'module': 'EXAMPLE-MIB', 'revision': None}, {'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}], 'expect': {'order': [0, 1], 'rule': 'source order; no MODULE-IDENTITY revision to compare', 'winner': 0}, 'id': 'module-one-undated-candidate-disables-the-comparison', 'op': 'module_precedence', 'why': 'An undated copy cannot be placed against a dated one, so a single undated candidate leaves the whole decision to source order -- the dated one does not win by default.'}, {'candidates': [{'module': 'SNMPv2-SMI', 'revision': None}, {'module': 'SNMPv2-SMI', 'revision': None}], 'expect': {'order': [0, 1], 'rule': 'source order; no MODULE-IDENTITY revision to compare', 'winner': 0}, 'id': 'module-all-undated-candidates-fall-to-configured-order', 'op': 'module_precedence', 'why': 'The usual case for the SMI modules themselves, which carry no MODULE-IDENTITY at all.'}, {'candidates': [{'module': 'EXAMPLE-MIB', 'revision': '202001010000Z'}, {'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}, {'module': 'EXAMPLE-MIB', 'revision': '201501010000Z'}], 'expect': {'order': [1, 0, 2], 'rule': 'newest MODULE-IDENTITY revision', 'winner': 1}, 'id': 'module-newest-of-three-wins-and-the-losers-keep-their-order', 'op': 'module_precedence', 'why': "The losers are reported as shadowed copies, so their order is part of the answer and not only the winner's identity."}, {'candidates': [{'module': 'EXAMPLE-MIB', 'revision': '202001010000Z'}, {'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}, {'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}], 'expect': {'order': [1, 2, 0], 'rule': 'newest MODULE-IDENTITY revision', 'winner': 1}, 'id': 'module-ties-among-the-newest-keep-configured-order', 'op': 'module_precedence', 'why': "The sort is stable: two copies sharing the newest revision are ranked against each other by the caller's order, not by chance."}, {'candidates': [{'module': 'EXAMPLE-MIB', 'revision': '202406010000Z'}], 'expect': {'order': [0], 'rule': '', 'winner': 0}, 'id': 'module-a-single-candidate-is-not-a-contest', 'op': 'module_precedence', 'why': 'Nothing was passed over, so no rule applies and none should be reported. A reader naming one here reports a precedence decision that was never taken.'}, {'expect': 'NEW-MIB', 'id': 'oid-newest-revision-wins', 'modules': [{'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2020-01-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'OLD-MIB', 'rfc': 0, 'tier': 0}, {'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'NEW-MIB', 'rfc': 0, 'tier': 0}], 'op': 'oid_precedence', 'why': 'The module-name rule, reached through the corpus ranking.'}, {'expect': 'LIVE-MIB', 'id': 'oid-obsolete-loses-to-live-however-new-it-is', 'modules': [{'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'obsolete', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'OBSOLETE-MIB', 'rfc': 0, 'tier': 0}, {'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2020-01-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'LIVE-MIB', 'rfc': 0, 'tier': 0}], 'op': 'oid_precedence', 'why': 'Obsolete outranks revision. A module whose every object is obsolete describes an arc nobody should decode against, and being republished later does not change that.'}, {'expect': 'STANDARD-MIB', 'id': 'oid-lower-tier-wins-over-a-newer-revision', 'modules': [{'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'VENDOR-MIB', 'rfc': 0, 'tier': 2}, {'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2020-01-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'STANDARD-MIB', 'rfc': 0, 'tier': 0}], 'op': 'oid_precedence', 'why': 'TIERS is (standard, draft, vendor). A vendor module republished yesterday does not displace the standard definition of an arc.'}, {'expect': 'IDENTITY-MIB', 'id': 'oid-module-identity-anchor-beats-object-identity', 'modules': [{'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2020-01-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'IDENTITY-MIB', 'rfc': 0, 'tier': 0}, {'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}, 'registration': {'class': 'objectidentity', 'name': 'registration', 'oid': '1.3.6.1.4.1.99999.1', 'status': 'current'}}, 'module': 'REGISTERING-MIB', 'rfc': 0, 'tier': 0}], 'op': 'oid_precedence', 'why': 'How strongly a module claims the arc sits between tier and revision, and MODULE-IDENTITY is the strongest claim there is: a module whose own identity is the arc owns it over one that registers it with an OBJECT-IDENTITY while being anchored elsewhere, even a newer one.'}, {'expect': 'LATER-MIB', 'id': 'oid-higher-rfc-breaks-an-equal-revision', 'modules': [{'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'EARLIER-MIB', 'rfc': 1213, 'tier': 0}, {'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'LATER-MIB', 'rfc': 3418, 'tier': 0}], 'op': 'oid_precedence', 'why': 'Two modules published on one day are ordered by the RFC number that carries them, later winning.'}, {'expect': 'A-MIB', 'id': 'oid-module-name-makes-the-rule-total', 'modules': [{'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'B-MIB', 'rfc': 0, 'tier': 0}, {'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2024-06-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'A-MIB', 'rfc': 0, 'tier': 0}], 'op': 'oid_precedence', 'why': 'Two candidates alike in every term still have to resolve the same way on every machine, so the name is the last term. Without it the winner is whichever the corpus happened to walk first, and a rebuild can change it.'}, {'expect': 'ANCHORED-MIB', 'id': 'oid-anchorless-module-loses-to-an-anchored-one', 'modules': [{'document': {'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'SMIV1-MIB', 'rfc': 0, 'tier': 0}, {'document': {'identity': {'class': 'moduleidentity', 'lastupdated': '2020-01-01 00:00', 'name': 'identity', 'oid': '1.3.6.1.4.1.99999.1'}, 'object': {'class': 'objecttype', 'maxaccess': 'read-only', 'name': 'object', 'nodetype': 'scalar', 'oid': '1.3.6.1.4.1.99999.1.1', 'status': 'current', 'syntax': {'class': 'type', 'type': 'Integer32'}}}, 'module': 'ANCHORED-MIB', 'rfc': 0, 'tier': 0}], 'op': 'oid_precedence', 'why': 'A module with no anchor at all is read off its ordinary definitions, below every anchor, so a TEXTUAL-CONVENTIONS-only or SMIv1 module cannot take an arc from the module that registered it. It carries no revision either -- the only place a module states one is a MODULE-IDENTITY, which is an anchor -- so this is also where the corpus rule parts from the module-name rule: an undated candidate loses here instead of disabling the comparison, because a corpus has no source order to fall back to.'})

What every implementation of the precedence rule must answer.

Each entry is {"id", "why", "op", ...}. op names the operation and the remaining keys are its arguments and its expected answer:

normalise_revision

stamp to the normalised form comparison happens in, or null where the stamp cannot be placed. Everything below rides on this: two implementations that agree on the ranking and disagree on the normalisation resolve differently anyway.

module_precedence

candidates, in configured order, to the order they rank in and which rule put the winner first. This is the rule pysnmp implements in MibBuilder._candidates and in CompositeMibCorpus.best_by_revision.

oid_precedence

modules, each with its tier and publishing RFC, to the module that owns CONTESTED. The corpus rule, which carries terms the module-name rule has nowhere to put.

why is not decoration. A vector that fails should say what broke without the reader’s author having to reconstruct the intent.

pysmi.corpus.precedence.run_vectors() list[str]

Answer every vector with pysmi’s own implementations.

The point of running them here as well as in pysnmp: a vector states what both sides must do, so it has to be checked against this side too or it only ever constrains the other one.

Returns:

The id of each vector that was answered wrongly, empty when all of them were answered right.

pysmi.corpus.precedence.vectors_as_json() str

The vectors as JSON, for a harness that is not written in Python.

Returns:

VECTORS rendered as an indented JSON array.

pysmi.corpus.precedence.write_vectors(directory: str) str

Write the vectors where another project’s harness can read them.

Parameters:

directory – where the file goes

Returns:

The path written.