Skip to content

v0.8.0

Choose a tag to compare

@gitkodak gitkodak released this 03 Aug 03:27
· 127 commits to main since this release

If you keep an overrides file, read this before upgrading. A file
containing wireless = "false", hide = "false", a fractional port, or a
misspelled key used to render; it now stops the run with an error naming the
problem. Those files were never doing what they said, which is why this changed,
but the failure is new and it is at the point of use. The fix in every case is
in the error message.

Changed

  • The documentation is split into docs/, and links into the README have
    moved.
    It had reached 1233 lines, which is past the point anybody reads,
    and is now 220: what the tool is, what it produces, how to install it, and
    how to see a map without touching your network. Everything else is a page of
    its own, indexed from the README.

    If you linked to a README anchor, it has moved. Every #section that is
    now a page is a different address:

    Was Now
    README.md#usage, #reading-the-diagram, #flag-reference docs/usage.md
    README.md#credentials, #unifi_api_key docs/credentials.md
    README.md#mapping-from-a-support-file docs/support-files.md
    README.md#json-for-programs, #mermaid-for-documentation docs/output.md
    README.md#sharing-a-map---obfuscate docs/sharing.md
    README.md#artwork-licensing-and-attribution docs/artwork.md
    README.md#how-it-works, #caveats docs/verification.md
    README.md#manual-overrides docs/overrides.md, which already existed

    Pages are organised by why somebody opens them rather than by what they are
    about, and each was then checked against what its own opening sentence
    promises. That found the artwork page describing where pictures come from with
    the answer two files away, the output page covering two formats of seven, and
    three pages repeating their own title as their first section. All were the
    same artefact: splitting on top-level headings moves text correctly and lands
    it by accident.

    The README's ## Manual overrides and ## Also planned sections are gone
    rather than moved, because docs/overrides.md and TODO.md already held the
    same material and the README was carrying second copies to drift against.

    The guards were widened before the split rather than after. Every link used to
    be a same-file #anchor; most are now docs/artwork.md#something, which can
    fail two ways a browser renders happily: the file may be missing, or present
    without the heading. The link check now resolves cross-file targets, the flag
    and command checks read every document rather than the README alone, and the
    generated flag reference lives in docs/usage.md. It caught fourteen links
    the split broke silently.

  • The documentation says which UniFi applications this has been run against,
    in the introduction rather than buried: Network for everything, and one
    Protect endpoint read purely to tell a camera from an Access reader. Devices
    from Access, Talk or a UNAS already draw, since they are clients or UniFi
    hardware like anything else, so the gap is narrower than "unsupported": what
    is missing is the second source that would let an ambiguous match resolve.
    CONTRIBUTING.md asks for those environments alongside the other things
    nobody here has.

  • Two released sections described one change twice. 0.7.0 had two entries for
    unifi-map shape, written days apart; 0.6.0 had two for RELEASING.md that
    contradicted each other, one saying a fix had been claimed and never made and
    the other making that same claim. Both are merged. A test now fails when a
    release describes the same subject twice, with genuinely separate changes to
    one thing listed as exceptions rather than the rule loosened.

  • The artwork page lists user-supplied artwork among its sources. An icon in
    an overrides file is where a picture comes from when none of Ubiquiti's
    catalogues has one, and it is the only source that works under --offline.

  • Both generated references were wrong in the same two ways, and neither
    staleness check could have noticed.
    docs/usage.md and the man page each
    printed a synopsis reading {fetch,render,all}, two commands behind, and the
    man page listed only those three under COMMANDS. Neither said that
    unifi-map overrides requires a check argument, because the introspection
    walked option_strings and a positional has none. All three lists are now
    derived from the parser.

    The two existing checks regenerate the file and fail on a diff, which catches
    an author who forgot to run make docs and cannot catch a generator holding a
    hardcoded list: it produces the same wrong file every time and compares equal
    to itself forever. A separate test now asserts that both documents name every
    subcommand and every positional. A document claiming it cannot drift from
    --help is worse than a hand-written one when it does, so the claim is tested
    rather than trusted.

  • overrides check now validates against the same topology render will
    build.
    It passed include_offline=True unconditionally, which is more
    permissive than the default render, so a selector naming a device the
    controller merely remembers passed the check and then failed the render it had
    just been checked for. That is the one outcome the command exists to prevent.
    --show-offline is now shared between the two subcommands rather than
    belonging to render alone.

  • An override that displaces a link the controller reported now says so.
    [[link]] and [[hosted]] both detach a node from its current parent before
    attaching the stated one, which is necessary and, for [[hosted]], the entire
    point: reparenting a VM under its hypervisor displaces a real observation by
    design. But the code assumed the displaced edge was always the "uplink not
    reported" placeholder, and nothing enforced that, so contradicting the
    controller was silent. It warns now, naming both ends. Tidying the placeholder
    stays quiet, since warning on the documented case is how a warning stops being
    read.

  • The runtime hint about unplaceable clients pointed at "Manual overrides in the
    README", a section the documentation split moved to docs/overrides.md.

  • The man page omitted exit code 3 (Graphviz not installed), and its
    description listed neither Mermaid nor JSON among the output formats.

  • Several documented behaviours did not match the code, all found by an external
    review reading the split documentation against the source. Each is a fix to
    the document except where noted:

    • docs/output.md said nothing is ever written to stdout; unifi-map shape
      writes its report there, which is what makes it pipeable.
    • It listed dot among the formats needing Graphviz. dot, mermaid and
      json are all written directly and work without it; only svg, pdf and
      png need it. Verified by rendering with an empty PATH.
    • Its JSON example named version 0.6.0 and omitted title and networks,
      the latter promised two paragraphs above it.
    • Its Mermaid example claimed to be the shipped demo while showing a direction
      and header no documented command produced. The example is now byte-identical
      to --layout tree output, with the one edit stated.
    • docs/overrides.md called [[device]].kind required; it defaults to
      unknown. It also never documented what note does, which differs per
      block: an edge label for [[link]] and [[hosted]], nothing at all for
      [[device]] and [[node]]. And it did not mention overrides check.
    • docs/usage.md said fetch downloads the icon font only when missing; it
      replaces any cached copy every time.
    • docs/support-files.md said omitting --fetch-fingerprints leaves clients
      without product artwork. The flag governs the download, not the lookup: a
      database already cached is read either way.
    • docs/verification.md said "all five output formats" when there are seven,
      and ended with a BEGIN GENERATED FLAGS marker that had no END and no
      content, left behind when the split moved the flag reference to
      docs/usage.md.
    • .env.example pointed at a Credentials section of the README that is now
      docs/credentials.md.
    • The README said Access readers, Talk phones and a UNAS "all still appear".
      docs/verification.md correctly calls the UNAS case inference, none of the
      three having been seen here, and the README now matches it.
  • Support-file operational guidance was on the artwork page. Site selection,
    the four archive limits, the compression-bomb defence and the slow-walk
    warning all sat inside a section about client icons, while
    docs/support-files.md covered none of them. Moved, and site selection and
    limits now come before the artwork asides, since a multi-site archive stops
    the run before artwork is reached.

  • CLAUDE.md proposed the JSON export, overrides check and the Mermaid export
    as future work, all three having shipped, and still described removing the
    sane layout alias in 0.6.0, which 0.6.0 did. The entries are rewritten to
    keep the constraints that still bind rather than deleted. The issue template
    for feature requests pointed contributors at that file's planned-work section,
    naming two shipped features; it points at TODO.md, which exists precisely
    because CLAUDE.md is written for agents.

  • Two defects in the override-displacement warning added earlier in this same
    unreleased cycle
    , both found by an external review before either reached a
    release:

    • It leaked under --obfuscate. Overrides are applied before obfuscation, so
      an ordinary obfuscated render logged the node's real label, its old parent's
      real label and the selector, contradicting the promise that log output is
      scrubbed too. The scrubbed diagram was the whole point, and a terminal
      beside it naming the nodes defeats it. Displacements are now carried out on
      ApplyResult for the caller to report, exactly as hidden nodes already
      were, so the policy sits with the code that knows about the flag. Under
      --obfuscate the warning survives as a count with no names.
    • It could call an asserted link controller-reported. Only the "uplink not
      reported" placeholder was excluded, so when two overrides reparented the
      same node, the second warning described the first override's own link as
      something the controller had reported: the exact misattribution the warning
      exists to prevent.
  • The documentation no longer counts its own external reviews. README.md,
    SECURITY.md and AI_DISCLOSURE.md each claimed a specific number of
    independent AI reviews, plus a total number of findings and a per-review
    breakdown. Reviews happen whenever a substantial change lands, so every one of
    those numbers was stale within days of being written and three files had to be
    edited in step to keep them honest. They now describe the practice: security,
    documentation, code and architectural review, more than one of each, by more
    than one system. Nothing else about the claim changed, including that every
    pass so far has found something its predecessors missed and that one finding
    stands declined with its reason recorded.

    While removing them, AI_DISCLOSURE.md said two findings were declined and
    later done anyway where SECURITY.md and CLAUDE.md both say one. Corrected
    to one, and it now names which.

  • The overrides guide said the displaced-link warning names both ends. It does,
    except under --obfuscate, where the fix above deliberately reduces it to a
    count. The exception is now stated where the guarantee is.

  • --support-max-archive was the one cap whose error did not name the flag that
    raises it, which is the worst of the four to omit it from, since it is the one
    a legitimately large site is most likely to hit. Now tested for all four.

  • Smaller documentation corrections: the support-file page called its four
    adjustable limits "three" twice; the overrides page said three of the four
    block types accept note when all four do; the JSON example said everything
    but nodes and edges was complete while abridging networks too; the
    README said Graphviz was required outright, when dot, mermaid and json
    need nothing installed; and three cross-references still pointed at README
    sections the split had moved (CLAUDE.md, the --icon-font help text, and
    the pull request checklist).

  • A misspelled section name was ignored entirely. Keys inside a block were
    checked and the block names were not, so [[lnik]] parsed, matched nothing,
    and the run reported "applies cleanly" with zero links: a file whose every
    line was ignored, reported as a success. Writing [link] instead of
    [[link]] is refused too, since TOML accepts it as a table and this file
    wants a list of them.

  • An SVG icon took its size from whatever was drawn inside it. Dimensions
    were read from the first 4 KiB rather than from the <svg> element, so a
    64x32 drawing containing <rect width="7" height="5"/> measured as 7x5 and
    rendered at a twelfth of its size.

  • An overrides file could mean the opposite of what it said. wireless = "false" and hide = "false" both read as true, because bool("false") is
    true and TOML has real booleans that are easy to quote by accident. port = true became port 1, since bool subclasses int. port = 1.9 became port

    1. And a misspelled key such as wirless was accepted and ignored, so the
      link simply stayed solid with nothing said. All four now stop the run, and the
      unknown-key error lists what the block does accept. This is the rule the
      feature already claimed: a stale or mistyped override fails loudly rather than
      quietly doing something else.

    This will stop a run that used to work, for anyone whose file contains one
    of those. That is the point rather than a side effect: a map drawn from a file
    meaning the opposite of what it says is worse than a run that stops and says
    where. Every message names the block, the key and what was expected.

  • SVG artwork was accepted and then broke the render. Graphviz refuses an
    SVG with no XML declaration, reporting it as a file that "was not found",
    which fails the whole run; measuring it here and letting it through turned a
    bad icon into a bad map. The accepted subset is now what Graphviz will
    actually load, verified by rendering one rather than by reading a spec, and
    width="." no longer raises while width="0.5" no longer becomes a 0x0 icon
    drawn as nothing.

  • Downloaded artwork bypassed the pixel cap. The guard was added to the path
    that measures files already on disk and not to the one that decodes bytes off
    the network, which is the path that matters most.

  • An icon whose path contains & was left in the output as a path. Graphviz
    writes it into an XML attribute, so it arrives as &amp; and never matched
    the permitted set, defeating the inlining that exists to keep local paths out
    of a file meant for sharing.

  • unifi-map shape could print the values it promises never to print.
    Unrecognised keys were named, filtered to "schema-shaped" tokens on the
    reasoning that a field name is controller schema worth seeing on an unfamiliar
    version. That filter accepted 10.0.0.5, nas, secretssid and
    branch-office, because a short lowercase token is exactly what an address, a
    hostname, an SSID and a site name look like, so a payload keyed by any of them
    was reproduced under a heading stating that could not happen. Unrecognised
    keys are now counted and never named. That loses the discovery of new field
    names, which was half the reason to run it elsewhere; a document claiming to
    be publishable has to be publishable first.

  • --obfuscate left real network names and ids in the JSON export. Aliases
    were built from the networks nodes referenced, so a configured network with no
    active clients was missed and kept its real name, and every network kept its
    real controller id regardless. The leakage test excluded JSON and Mermaid
    despite being named for every output format; it now covers both, and the
    fixture carries an unused network with an identifying name.

  • A fresh install could not fetch the hardware catalogue at all. _fetch
    returns a small Fetched object rather than a requests.Response, and the
    catalogue loader called .json() on it, which does not exist. The resulting
    AttributeError was not caught by the surrounding handler, so the very first
    render on a machine with an empty cache and a reachable CDN crashed. Every
    test either seeded the cache or simulated a network failure, so the ordinary
    success path was the one thing never exercised; it is now.

  • --obfuscate erased the difference between an asserted link and an observed
    one.
    Edges are rebuilt field by field during obfuscation and asserted was
    not carried, so a link stated in an overrides file came out drawn exactly like
    one the controller reported. That distinction is the project's central promise
    and --obfuscate is the mode where a reader is least able to check it. A test
    now walks the dataclass, so a field added to Edge or Network fails until it
    is either carried through obfuscation or deliberately exempted.

  • A cached snapshot could mix two fetches. write() only wrote the payloads
    it had while read() loads every recognised file present, so an endpoint that
    succeeded once and failed later left its old file to be read beside fresh
    data. Switching one cache directory between a live fetch and a support file
    did the same. A snapshot is now a complete generation: recognised files absent
    from the new fetch are removed, and nothing else in the directory is touched.

  • The diagram's own subtitle counted the wrong things. It was computed
    before overrides were applied, so a map that declared devices or hid nodes
    stated the pre-override totals underneath itself.

  • An override could not be trusted to catch every loop. The cycle check kept
    one parent per node and followed only that, which is exact for the graphs this
    tool builds today but is a property of its callers rather than of the check. A
    cycle reachable only through a second parent went undetected. Now a full
    depth-first search, iterative so a malformed graph cannot exhaust the stack.

  • A user-supplied SVG or JPEG icon left an absolute path in the output. The
    SVG post-pass inlined only .png, so other formats stayed as filesystem
    references, disclosing a local path (usually containing a username) in a file
    whose purpose is to be shared. draw.io export had the matching bug from the
    other side, labelling every icon image/png whatever it was.

  • The image size cap was not a cap. Pillow warns at MAX_IMAGE_PIXELS and
    only raises at roughly twice it, so an image up to double the limit decoded
    anyway, and neither exception it raises derives from those being caught. The
    guard is now scoped to each operation with catch_warnings rather than
    changing the whole process's warning filters, and covers _measure, which
    every cached and user-supplied image passes through.

  • SVG artwork in an overrides file never worked, though it is documented as
    working: measuring went through Pillow, which does not decode SVG, so every
    SVG was refused before Graphviz saw it. Dimensions are now read from the file,
    by regex rather than an XML parser, since this is a file somebody else may
    have written. An SVG with only a viewBox is still refused, because Graphviz
    ignores those silently and a named error beats a blank node.

  • Mermaid identifiers deleted punctuation rather than replacing it, so
    asserted-a-b and asserted-ab became the same node. Labels and titles now
    also survive a newline, which previously ended the statement carrying them and
    let the rest of a device name be read as Mermaid source.

  • unifi-map shape counted configured networks as "client networks", including
    ones no client is on, and reported every field of the topology graph as absent
    because that payload is a single object rather than a list of records.

  • Smaller: a size argument that overflows a float is now a clean error rather
    than a traceback, --support-max-entries refuses zero and negatives, private
    directories are created private rather than tightened a moment later, and
    Network carries is_guest, which the JSON export had been trying to read
    through a getattr that could never succeed.

Added

  • Nine device icons, drawn by this project rather than fetched. Ubiquiti's
    artwork covers their hardware and the clients their fingerprint database
    recognises; everything else fell through to a bare Graphviz primitive, so an
    access point was a trapezium and an unplaceable client was a diamond.

    Five are infrastructure, keyed on the device's role. Four are clients, split on
    guest and wireless, which is the same four-way split the console's own icon
    font encodes. Those four close a gap with no other answer: that font is
    served only by a controller and is absent from a support file, so mapping an
    archive without touching a console left unidentified clients as shapes. It no
    longer does, which removes the last reason a support-file user needs a
    controller.

    They appear in two places. --icons builtin no longer means "no artwork"; it
    means artwork that is ours, the Internet cloud included, and produces a
    complete map with no network access at all. And inside --icons unifi they
    are the fallback for hardware absent from Ubiquiti's catalogue, which is a
    small, deliberate step away from "unifi shows exactly what the console
    shows": on the shipped demo it changes exactly one node. Devices the catalogue
    covers are untouched.

    One defect found before any of this shipped, recorded because it is the kind
    that hides: render_dot discarded the resolved icons entirely unless the icon
    set was unifi, a leftover from when builtin meant no artwork existed. The
    icons drew correctly and never reached the map. The same gate had been
    silently dropping icons supplied through an overrides file, which are not
    fetched from anywhere either, so that is fixed too.

    Nine places across the README, four pages under docs/ and SECURITY.md still
    promised "plain shapes" or "geometric shapes" for --icons builtin and for
    unidentified clients. All now describe what happens. Two related corrections
    came out of the same sweep: the artwork page presented one precedence order
    when the implementation has three (infrastructure, clients and the Internet
    node resolve by different code, and the ISP brand mark was missing from the
    table entirely), and the transparency note said builtin nodes keep a filled
    background of their own, which stopped being true when the fallback shapes
    became transparent PNGs.

    Drawn with Pillow, which was already a hard dependency, so nothing new is
    required and nothing is vendored. The silhouette carries the meaning rather
    than the colour, with guest drawn hollow rather than in a second hue, so the
    set survives greyscale and colourblind readers. Aspect ratios are real, so a
    switch is wide and short and a handset is taller than it is wide. Each theme
    colour caches separately.

  • The shipped demo marks its guest client as one. is_guest is a separate fact
    from sitting on the guest VLAN, and without it two of the nine new icons never
    appeared in the demo at all.

  • Every page under docs/ links back to the documentation index. A search
    result or a shared link lands a reader on one page of a set, with nothing on
    it saying that a set exists or where it is listed. Suggested by an external
    documentation review, and guarded, because the convention is the sort that
    holds across eight files and quietly lapses on the ninth.

  • Three documentation guards, each for a class of drift the existing checks
    could not see.
    The JSON example is parsed and its version and top-level keys
    compared against real output; the Mermaid example is diffed byte-for-byte
    against what its stated command produces; and every document is checked for a
    generated-content marker without its pair. All three were mutation-tested by
    reintroducing the exact defect they were written for.