oct
Loading...
Searching...
No Matches
waivers Namespace Reference

Classes

class  WaiverOutcome

Functions

list[tuple[int, str]] _comment_tokens (str text)
dict[int, dict] parse_inline_waivers (str text)
frozenset[str] inline_disabled_rules (str text)
list[tuple[int, str]] split_scanner_findings (str message)
WaiverOutcome apply_waivers (str message, dict[int, dict] waivers, str rule)

Variables

int WAIVER_LINE_SPAN = 2
 LINE_SCOPED_RULES = frozenset({"no_hardcoded_secrets"})
 _WAIVER_RE
 _FINDING_PREFIX_RE = re.compile(r'^L(?P<lineno>\d+):\s')
 _WAIVER_MARKER_RE = re.compile(r'OCT-LINT', re.IGNORECASE)

Detailed Description

Purpose
-------
OI-540 - shared inline-waiver parsing for OCT-LINT comments.
Plan 193 - waiver SCOPE: a waiver excuses the finding beside it, not the
whole file.

Extracted from :mod:`oct.linter.oct_lint` so that both the linter and
the quality gate (:mod:`oct.git.quality_gate`) can import the same
parser without the quality gate depending on the linter.

Plan 193 (sec) closed a hole this module sat at the centre of. The
parser always returned line numbers, but BOTH consumers threw them away
and treated any waiver as whole-file suppression - so a real credential
pasted anywhere into one of the ~200 files carrying a
``no_hardcoded_secrets`` waiver committed clean. Scope is now:

- ``# OCT-LINT: disable=<rule> reason="…"`` - LINE scope. Covers its own
  line and the immediately following line (:data:`WAIVER_LINE_SPAN`), so
  both trailing-comment and comment-above placements work and the waiver
  travels with the code it excuses.
- ``# OCT-LINT: disable-file=<rule> reason="…"`` - FILE scope. Whole-file
  blindness stays available, but as a deliberate, greppable choice.
- A bare ``disable=`` waiver adjacent to NO finding of its rule keeps its
  historical file-wide power (so the pre-193 tree does not storm the gate
  in one landing) and reports its line via
  :attr:`WaiverOutcome.legacy_lines`, which the linter surfaces as the
  advisory ``legacy_file_scoped_waiver`` burn-down finding. When that
  count reaches zero the legacy branch can be deleted as a no-op.

Reasonless waivers remain inert in both forms (pre-existing rule).

Responsibilities
----------------
- Host :data:`_WAIVER_RE` - the canonical regex for
  ``# OCT-LINT: disable[-file]=<rule> [reason="..."]`` comments.
- Provide :func:`_comment_tokens` - tokenise source so only real
  comments can carry a waiver.
- Provide :func:`parse_inline_waivers` - returns a mapping of line
  numbers to ``{"rule": str, "reason": str|None, "scope": str}`` dicts.
- Provide :func:`inline_disabled_rules` - the FILE-WIDE answer the
  linter reaches from that mapping, written once so every consumer
  reaches it by the same route (plan 277).
- Provide :func:`split_scanner_findings` - recover per-finding line
  numbers from the secret scanner's ``"; "``-joined message.
- Provide :func:`apply_waivers` - scope waivers over those findings and
  return a :class:`WaiverOutcome`.

Diagnostics
-----------
Domain: OCT-CORE
Levels:
    L2 - (none) pure parsing helper; it reports no lifecycle of its own
    L3 - (none) callers report waiver outcomes in their own domains
    L4 - parse and scope entry: input size, and the rule being scoped

Contracts
---------
- Must not import :mod:`oct.linter.oct_lint` (no circular dependency).
- Return shape is stable and additive - both linter and quality gate
  depend on it. Plan 193 added the ``"scope"`` key; existing readers of
  ``"rule"`` / ``"reason"`` are unaffected.
- This module decides waiver SCOPE only. It contains no threshold, no
  heuristic, and no secret pattern, and it never changes what the
  scanner detects - only which detected findings are excused.
- :func:`inline_disabled_rules` can never return a member of
  :data:`LINE_SCOPED_RULES`, by construction. Those rules are scoped per
  finding by :func:`apply_waivers` and must never be switched off
  wholesale by a consumer that reads this set (plan 277 - the reason
  G-D4 is untouched by the format pass honouring waivers).
- A waiver is only honoured when it appears in a real ``COMMENT``
  token (193 R1). Waiver-shaped text inside a string literal is inert,
  so a file that documents or tests the waiver syntax cannot waive
  itself. Unparseable input yields NO waivers (fail closed).
- :func:`apply_waivers` is fail-closed: any finding whose line cannot be
  recovered (line ``0``) is unreachable by line scope and survives
  unless an explicit file-scope waiver covers it.

Dependencies
------------
- re (waiver + finding-prefix pattern matching)
- tokenize (comment-token extraction - 193 R1)
- io (StringIO line reader fed to tokenize - 193 R1)
- dataclasses (the frozen WaiverOutcome record)
- oct.core.diagnostics (_dbg structured logger)

Function Documentation

◆ _comment_tokens()

list[tuple[int, str]] waivers._comment_tokens ( str text)
protected
Return ``(lineno, comment_text)`` for every real comment in *text*.

Plan 193 R1 - a waiver is a COMMENT, so only comment tokens are
offered to the waiver regex. Waiver-shaped text inside a string
literal (a docstring that documents the syntax, a test fixture that
exercises it, a rule-registry example) is therefore inert, and can
no longer silently waive the file that merely talks about waivers.

FAIL CLOSED: if the source cannot be tokenised, this returns no
comments at all, so the caller honours NO waivers and every finding
survives. That matches the 170/176 doctrine - an unparseable file
must never gain suppression it could not earn.

Definition at line 164 of file waivers.py.

Here is the caller graph for this function:

◆ apply_waivers()

WaiverOutcome waivers.apply_waivers ( str message,
dict[int, dict] waivers,
str rule )
Scope *rule*'s waivers over the findings in *message*.

*waivers* is the mapping returned by :func:`parse_inline_waivers`.
Resolution order:

1. An explicit ``disable-file=`` waiver for *rule* suppresses every
   finding (deliberate whole-file blindness).
2. Each line waiver suppresses findings on its own line and the next
   (:data:`WAIVER_LINE_SPAN`).
3. If findings survive AND at least one bare waiver matched nothing,
   that waiver falls back to pre-193 whole-file suppression and is
   reported in :attr:`WaiverOutcome.legacy_lines`.

Reasonless waivers are ignored at every step.

Definition at line 284 of file waivers.py.

Here is the call graph for this function:

◆ inline_disabled_rules()

frozenset[str] waivers.inline_disabled_rules ( str text)
Return the rules *text*'s inline waivers switch off for the whole file.

This is the linter's own set, and the ONE place it is written. It is
:func:`oct.linter.oct_lint._lint_single_file`'s ``waived_rules``
verbatim - a waiver counts when it carries a reason and names a rule
outside :data:`LINE_SCOPED_RULES`, and its ``scope`` is not consulted,
because for a rule whose check reports a verdict rather than located
findings there is nothing to scope against. That is why the bare
``disable=`` form disables its rule file-wide here exactly as the
linter's ``_rule_active`` gate has always let it.

Plan 277 added this so the quality gate's format pass
(:func:`oct.git.quality_gate._run_format_pass`) and the health
dashboard (:mod:`oct.health.oct_health`) can reach the linter's
answer without restating it. Before, each pass knew a different
subset of what excuses a file, and a module the operator had ruled
exempt from ``dbg_import`` linted clean and was refused by the format
pass on the same rule, in the same commit.

A member of :data:`LINE_SCOPED_RULES` can never appear in the result.
Those rules are excused per finding by :func:`apply_waivers` and a
consumer that switched one off file-wide from here would be handing
back exactly the whole-file blindness plan 193 (sec) closed.

FAIL CLOSED on unparseable input: :func:`parse_inline_waivers` yields
no waivers when the source cannot be tokenised, so this yields no
disabled rules and every check runs.

Definition at line 219 of file waivers.py.

Here is the call graph for this function:

◆ parse_inline_waivers()

dict[int, dict] waivers.parse_inline_waivers ( str text)
Parse ``# OCT-LINT: disable[-file]=<rule> [reason]`` comments from source.

Returns a dict mapping line numbers (1-based) to
``{"rule": str, "reason": str|None, "scope": "line"|"file"}``.

``scope`` is ``"file"`` only for the explicit ``disable-file=`` form;
the bare ``disable=`` form is ``"line"``. Waivers with no reason are
still returned (the linter warns about them) but callers must treat
them as inert.

Plan 193 R1: only real comment tokens are considered - see
:func:`_comment_tokens` for the rationale and the fail-closed
behaviour on unparseable input.

Definition at line 189 of file waivers.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ split_scanner_findings()

list[tuple[int, str]] waivers.split_scanner_findings ( str message)
Split a scanner message into ``(lineno, text)`` findings.

:func:`oct.tools.secret_scanner.check_no_hardcoded_secrets` joins its
violations with ``"; "`` and prefixes each with ``L<lineno>: ``. A
truncated preview may itself contain ``"; "``, so a fragment without
that prefix is folded back into the preceding finding rather than
being mistaken for a new one. A leading fragment with no prefix (the
unparseable-file message) is reported at line ``0``, which no line
waiver can reach.

Definition at line 260 of file waivers.py.

Here is the caller graph for this function:

Variable Documentation

◆ _FINDING_PREFIX_RE

waivers._FINDING_PREFIX_RE = re.compile(r'^L(?P<lineno>\d+):\s')
protected

Definition at line 119 of file waivers.py.

◆ _WAIVER_MARKER_RE

waivers._WAIVER_MARKER_RE = re.compile(r'OCT-LINT', re.IGNORECASE)
protected

Definition at line 126 of file waivers.py.

◆ _WAIVER_RE

waivers._WAIVER_RE
protected
Initial value:
1= re.compile(
2 # OCT-LINT: disable=no_hardcoded_secrets reason="Waiver regex pattern, not a secret"
3 r'#\s*OCT-LINT:\s*disable(?P<file_scope>-file)?\s*=\s*(?P<rule>[a-z_]+)(?:\s+(?P<reason>.*))?',
4 re.IGNORECASE,
5)

Definition at line 112 of file waivers.py.

◆ LINE_SCOPED_RULES

waivers.LINE_SCOPED_RULES = frozenset({"no_hardcoded_secrets"})

Definition at line 110 of file waivers.py.

◆ WAIVER_LINE_SPAN

int waivers.WAIVER_LINE_SPAN = 2

Definition at line 104 of file waivers.py.