Skip to content

ci: make public-hygiene-lint green - #2

Merged
MattJackson merged 2 commits into
mainfrom
ci/public-hygiene-green
Aug 9, 2026
Merged

ci: make public-hygiene-lint green#2
MattJackson merged 2 commits into
mainfrom
ci/public-hygiene-green

Conversation

@MattJackson

Copy link
Copy Markdown
Contributor

public-hygiene-lint was RED on main in this repo. Every hit is fixed by rewriting the text to
state the BEHAVIOUR rather than our internal process. No allow markers were added anywhere in
this change: a blanket allow would turn the gate into decoration.

Hits found on main, and the decision for each

  • .github/scripts/next-version.sh + .github/workflows/release-selftest.yml [internal-issue-id]
    cited an internal guard id. REWRITTEN: id dropped.
  • README.md x12 [internal-issue-id] used GAP #n, the shape of a tracker citation, for sections
    DEFINED IN THIS SAME DOCUMENT. REWRITTEN as Gap n: they are headings a reader can scroll to,
    not tickets. No content changed; the document is unaltered apart from the label.
  • auth-ldap/src/{groups,lib,tests}.rs x3 [internal-issue-id] pointed at those README sections.
    REWRITTEN to the new heading names, so the pointer still resolves for a public reader.
  • auth-ldap/src/tests.rs x17 [internal-issue-id] prefixed doc comments with Finding #n: /
    Round-2 finding #n:, which cite review rounds a reader cannot open. REWRITTEN by dropping the
    prefix: every one of those comments already states the behaviour being pinned in the next
    clause, so nothing is lost.

Red before green

Same invocation CI uses. Output is ASCII-folded for this description; the runs are verbatim
otherwise.

Before (on main)

$ python3 busbarAI/scripts/public-hygiene-lint.py --root .
== public-hygiene: what a customer reads, in . ==
  scanned 17 file(s) via git ls-files; 11 rules applied

  [internal-issue-id] internal issue / task / audit-round identifier - 34 hit(s)
      why: cites a tracker or audit artifact the reader cannot open; `task #141` and `R27 #8` shipped inside operator-facing API documentation
      .github/scripts/next-version.sh:5: # release automation can't silently rot (guard #135.8). Prints "v<MAJOR>.<MINOR>.<PATCH>" to stdout.
      .github/workflows/release-selftest.yml:1: # CI self-test for the release-on-upstream version-compute logic (guard #135.8).
      README.md:26: ### [red] GAP #1 - No `LoginOutcome` variant to render a credential FORM  .  **[resolved] RESOLVED (v2)**
      README.md:55: ### [red] GAP #2 - The chooser can't classify credential-vs-redirect, and the login-button config is OAuth-only  .  **[resolved] RESOLVED (v2)**
      README.md:63: `begin_login` - which for LDAP just fails closed (GAP #1). (b) The gate that decides a plugin can serve the
      README.md:83: ### [amber] GAP #3 - The password crosses to the plugin with no redaction  .  **[resolved] RESOLVED (v2)**
      README.md:106: ### [green] GAP #4 - Plugin-opens-socket: **NOT a gap.** (Verified allowed.)
      README.md:120: ### [green] GAP #5 - LDAPS/TLS, CA, bind-DN template, base-DN, group attr: **mostly fit; one small secret-ref gap**
      README.md:131: ### [green] GAP #6 - Group DN -> role normalization is pushed entirely onto the plugin  .  **additive-later**
      README.md:139: ### [amber] GAP #7 - No verdict distinct from `Reject` for "wrong password (retry)" vs "directory down"  .  additive-later
      README.md:144: is only additive if `LoginOutcome` is already being reopened for GAP #1 (in which case land it together).
      README.md:156: - **Plugin-opens-socket is allowed** (GAP #4) - no sandbox; same in-process dlopen as vault.
      README.md:157: - **Opaque `settings`** carry every LDAP knob without the engine understanding any of them (GAP #5).
      README.md:184: with zero remaining blockers. GAP #4-async, #5-secret-ref, #6, #7 stay **additive-later** and do not block the
      auth-ldap/src/groups.rs:8: //! ## The group -> role mapping gap (README gap #6)
      auth-ldap/src/lib.rs:138: /// secret resolution for plugin-opened connections). See README gap #5.
      auth-ldap/src/tests.rs:5: //! injection defense, LDAP filter escaping, and the group-DN -> role mapping (the README gap #6
      auth-ldap/src/tests.rs:72: /// Finding #5: with a search filter set, a service DN but NO password would make `simple_bind(dn,
      auth-ldap/src/tests.rs:86: /// Round-2 finding #1: a service DN with an EMPTY-STRING password still yields `simple_bind(dn, "")`
      auth-ldap/src/tests.rs:101: /// Round-2 finding #2: a plaintext `ldap://` URL to a NON-loopback host with no STARTTLS sends the
      auth-ldap/src/tests.rs:181: /// Finding #1: `ca_cert_pem` is documented but never wired into TLS. `new()` must FAIL CLOSED rather
      auth-ldap/src/tests.rs:193: /// Finding #4: the service-account password must never appear in a `Debug` dump of the config (it
      auth-ldap/src/tests.rs:274: /// Finding #9: `\XX` hex-pair DN escapes (RFC 4514) must be resolved, not left literal. Consecutive
      auth-ldap/src/tests.rs:289: /// Finding #2: the operation timeout is derived from `timeout_secs`. (The full per-op wiring needs a
      auth-ldap/src/tests.rs:421: // -- Fake-backend coverage of the bind path (finding #6 seam) -------------------------------------
      auth-ldap/src/tests.rs:512: /// Finding #6 (reject path): a wrong password -> non-zero bind rc -> `InvalidCredentials`.
      auth-ldap/src/tests.rs:525: /// Finding #6 (reject path): a search that matches nothing -> `InvalidCredentials`, without ever
      auth-ldap/src/tests.rs:539: /// Finding #6 (success path) + #3 (normalized id): a successful search-then-bind returns `Identify`
      auth-ldap/src/tests.rs:564: /// Finding #7: a search matching >1 entry is AMBIGUOUS and must be rejected, not silently bound to
      auth-ldap/src/tests.rs:590: /// Finding #3: two username casings resolve to the SAME principal id (direct-template mode).
      auth-ldap/src/tests.rs:613: /// Finding #8: an oversized `memberOf` is capped so a hostile directory cannot exhaust memory.
      auth-ldap/src/tests.rs:630: // -- Round-2 finding #3: every Directory->Reject branch actually rejects ----------------------------
      auth-ldap/src/tests.rs:708: // -- Round-2 finding #4: the search-then-bind path validates username / resolved DN ----------------
      auth-ldap/src/tests.rs:750: /// Round-2 finding #5: `principal_id` must be Unicode-aware. `to_ascii_lowercase` leaves non-ASCII

== result ==
  17 public file(s) scanned against 11 rules - 34 hit(s), 0 allowed
  public-hygiene-lint FAILED
  These lines describe how the software was BUILT, not what it does, in files a
  customer can read. Rewrite the text to state the behaviour or the invariant; if a
  line is genuinely legitimate, mark it:
    # public-hygiene-lint: allow - <why this text belongs in a public file>

After (this branch)

$ python3 busbarAI/scripts/public-hygiene-lint.py --root .
== public-hygiene: what a customer reads, in . ==
  scanned 17 file(s) via git ls-files; 11 rules applied

== result ==
  17 public file(s) scanned against 11 rules - 0 hit(s), 0 allowed
  public-hygiene-lint passed

Matthew Jackson added 2 commits August 8, 2026 16:00
The public-hygiene gate flags text in customer-readable files that
describes how the software was BUILT rather than what it DOES. Every hit
in this repo is rewritten to state the behaviour or the invariant; no
allow markers are added, so the gate keeps its teeth.
busbar 1.5.3 retired two config surfaces this fixture still used: `auth.methods:`
and INLINE module entries under `auth.admin_auth:`. An identity provider is now
defined ONCE under `identity-providers:` -- `browser_login` and `settings`
hanging off that single definition -- and referenced elsewhere by bare name.

A config still carrying the old shape is not merely deprecated: core recognises
it as a 1.x config and refuses to start, so the fixture described a gateway that
could never come up. All the test could report was the symptom, a long way from
the cause:

  test ldap_*_flow ... FAILED
  panicked at tests/e2e.rs: busbar exited early during health poll: exit status: 1

Reproduced by rendering this fixture's own YAML and feeding it to a `busbar`
built from the core branch CI tests against:

  [error] config.yaml: this looks like a busbar 1.x config; run
          `busbar --migrate-config <config.yaml>` and review the flagged items.
          1.x markers found:
    - `auth.methods:` (retired 1.5.3 -> the matching `identity-providers:`
      definition (browser_login + settings are per-provider))
    - `auth.admin_auth:` carries INLINE module entries (retired 1.5.3 -> define
      the provider once under `identity-providers:` and reference it by bare name)

The replacement shape is core's own `--migrate-config` output for the previous
fixture, confirmed by re-validating: the 1.x gate is cleared and the only
remaining complaint is that the ldap plugin is not in the plugins directory,
which is precisely what this test puts there.
@MattJackson
MattJackson merged commit 05f8485 into main Aug 9, 2026
1 check passed
@MattJackson
MattJackson deleted the ci/public-hygiene-green branch August 9, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant