Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Smoke

# BON-354 — release-artifact smoke gate.
# Release-artifact smoke gate.
#
# Builds the v0.1.0 wheel + sdist, twine-checks both, then installs each
# into a fresh venv and runs `tests/smoke/smoke_test.py` (stdlib only, no
Expand All @@ -10,8 +10,7 @@ name: Smoke
# Mocked-only: this workflow does NOT use any LLM API key. The real-key
# leg lives in the local `tests/e2e/scripts/e2e-box.sh` per
# `docs/release-gates.md` §"E2E box · Local execution only · Never runs
# in CI." The `bonfire dispatch` CLI verb is deferred to v0.1.1 (BON-633)
# per BON-354 comment 9503cd7f.
# in CI." The `bonfire dispatch` CLI verb is deferred to v0.1.1.

on:
push:
Expand All @@ -29,8 +28,7 @@ jobs:
fail-fast: false
matrix:
# Today only 3.12 is declared in `pyproject.toml` classifiers.
# Add 3.13 here once the classifier lands (scout report §5
# out-of-scope row 3, D-FT BON-354-FT-7).
# Add 3.13 here once the classifier lands.
python-version: ["3.12"]
steps:
- name: Checkout
Expand Down
18 changes: 9 additions & 9 deletions src/bonfire/analysis/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@


class CartographerBudget(BaseModel):
"""Frozen budget of Cartographer tunables (BON-226 §5)."""
"""Frozen budget of Cartographer tunables."""

model_config = ConfigDict(frozen=True, extra="forbid")

Expand All @@ -64,7 +64,7 @@ class CartographerBudget(BaseModel):
)
enrichment_enabled: bool = Field(default=False)

# ─── BON-294 Wave 2c.1 enrichment delta ──────────────────────────
# ─── enrichment delta ─────────────────────────────────────────────
enrichment_mode: Literal["off", "harvest", "llm"] = Field(default="harvest")
enrichment_top_n: int = Field(default=20, ge=1, le=500)
enrichment_batch_size: int = Field(default=5, ge=1, le=50)
Expand Down Expand Up @@ -94,7 +94,7 @@ class RankedNode(BaseModel):
min_length=1,
description="Projected TreeContext signature text. Never empty (§12 M5). "
"The non-empty invariant is enforced at the model layer via "
"``min_length=1`` so an external caller (BON-231 composition root, "
"``min_length=1`` so an external caller (the composition root, "
"a future plugin, the serializer round-trip path) cannot construct "
"an invalid ``RankedNode`` with ``snippet=''``.",
)
Expand All @@ -106,7 +106,7 @@ class RankedNode(BaseModel):
symbol_rank: float = Field(ge=0.0)
edge_weight_in: float = Field(ge=0.0)

# ─── BON-294 Wave 2c.1 enrichment delta ──────────────────────────
# ─── enrichment delta ─────────────────────────────────────────────
summary: str | None = Field(default=None, max_length=500)
summary_source: Literal[
"docstring",
Expand Down Expand Up @@ -206,23 +206,23 @@ class ProjectAnalysis(BaseModel):
)
rendered_map: str

# BON-303 Wave 3a.4 — discovered gaps for DiscoveredIntentSource.
# discovered gaps for DiscoveredIntentSource.
# Default empty list preserves all existing tests that construct
# ProjectAnalysis() without gaps.
gaps: tuple[GapFinding, ...] = Field(default=())

@field_validator("study_schema_version")
@classmethod
def _require_v2_schema(cls, v: int) -> int:
# BON-294 Wave 2c.1 A10 — reject v1 cache blobs so Wave 2b cache
# reads fall back to a fresh scan instead of silently returning
# a study missing the enrichment fields.
# reject v1 cache blobs so cache reads fall back to a fresh scan
# instead of silently returning a study missing the enrichment
# fields.
if v != 2:
raise ValueError(f"study_schema_version must be 2, got {v}")
return v

def to_bytes(self) -> bytes:
"""Gzip-compressed JSON — BON-231 Wave 2b cache seam.
"""Gzip-compressed JSON cache seam.

The two-byte gzip magic (``\\x1f\\x8b``) lets any cache layer
sniff the envelope without deserialising. ``gzip`` is imported
Expand Down
105 changes: 21 additions & 84 deletions tests/unit/test_no_bon_ref_in_src_sweep.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
"""RED sweep test — no stale ``BON-NNN`` refs in ``src/bonfire/`` (BON-353).
"""Sweep test — no internal tracker refs (``BON-NNN``) in ``src/bonfire/``.

Locks in audit items **B1, B2, B3** from the BON-353 doc-polish audit
(``docs/audit/scout-reports/bon-353-audit-20260427T164458Z.md``).
This is a public tree, so internal tracker IDs must never ship in source.
The sweep walks every ``.py`` file under ``src/bonfire/`` and scans for the
regex ``BON-\\d+``; any hit is an offender.

Walks every ``.py`` file under ``src/bonfire/`` and scans for the regex
``BON-\\d+``. Any hit that is NOT in the explicit allowlist below is an
offender — the Warrior must scrub it (or, if it's a legitimate citation
of canonical decision authority, extend the allowlist with rationale).

The allowlist is keyed by ``(relative_path, line_number,
expected_substring_prefix)``. The substring prefix is matched against
the START of the offending line (after stripping). Mismatch on any
field causes the test to fail — i.e. if a code edit shifts a citation
to a new line number, the allowlist must be re-confirmed too. That's
the durability we want: every BON-NNN ref in source costs an explicit
allowlist entry.

Expected RED-state failures at HEAD (Warrior must scrub these):

* ``src/bonfire/protocols.py:74`` — ``BON-338`` (audit B1)
* ``src/bonfire/dispatch/security_hooks.py:1`` — ``BON-338`` (audit B2)
* ``src/bonfire/dispatch/security_patterns.py:1`` — ``BON-338`` (audit B3)

Allowlisted (legitimate sweep-guard / decision-authority citations):

* ``src/bonfire/cli/app.py:16`` — ``BON-345`` sweep-guard
* ``src/bonfire/cli/commands/persona.py:14`` — ``BON-345`` sweep-guard
* ``src/bonfire/analysis/models.py`` (BON-347) — Sage decision/Wave
citations carried over from the BON-347 port; canonical authority
pointers, not task-pointer rot.
The allowlist is empty by policy — there is no sanctioned exception. A
tracker ID introduced into ``src/bonfire/`` therefore fails the gate
outright; the fix is always to rewrite the comment or docstring into
neutral prose that keeps the engineering intent and drops the ID.

Reads files on disk only — no subprocess.
"""
Expand All @@ -42,63 +21,20 @@
_REPO_ROOT = Path(__file__).resolve().parents[2]
_SRC_DIR = _REPO_ROOT / "src" / "bonfire"

# Regex matching the canonical ticket-ref shape (e.g. ``BON-338``).
# Note: ``BON-W5.3``-style refs do NOT match (W is not a digit) — those
# are intentionally out of scope for this sweep.
# Regex matching the internal tracker-ref shape: the ``BON-`` prefix
# followed by one or more digits. Suffixes that are not pure digits (a
# letter after the dash) do not match and are out of scope for this sweep.
_BON_REF = re.compile(r"BON-\d+")

# ---------------------------------------------------------------------------
# Allowlist
# ---------------------------------------------------------------------------
# Each entry = (relative_path_from_src_bonfire, line_number, expected_line_prefix).
# ``expected_line_prefix`` is matched with ``.lstrip().startswith(...)`` so
# whitespace at line start is normalised. This couples the allowlist tightly
# to the actual source — a legitimate edit elsewhere in the file is fine, but
# a change to the ref-bearing line itself forces a fresh review.
_ALLOWLIST: frozenset[tuple[str, int, str]] = frozenset(
{
# --- BON-345 sweep-guards retired in v0.1.0a1 ----------------
# Falcor became the shipped default persona; the literal was no
# longer banned, so the obfuscation hack was removed and these
# allowlist entries with it.
# --- BON-347 analysis port (canonical Sage / Wave citations) --
(
"analysis/models.py",
47,
'"""Frozen budget of Cartographer tunables (BON-226 §5)."""',
),
(
"analysis/models.py",
67,
"# ─── BON-294 Wave 2c.1 enrichment delta ──────────────────────────",
),
(
"analysis/models.py",
97,
'"``min_length=1`` so an external caller (BON-231 composition root, "',
),
(
"analysis/models.py",
109,
"# ─── BON-294 Wave 2c.1 enrichment delta ──────────────────────────",
),
(
"analysis/models.py",
209,
"# BON-303 Wave 3a.4 — discovered gaps for DiscoveredIntentSource.",
),
(
"analysis/models.py",
217,
"# BON-294 Wave 2c.1 A10 — reject v1 cache blobs so Wave 2b cache",
),
(
"analysis/models.py",
225,
'"""Gzip-compressed JSON — BON-231 Wave 2b cache seam.',
),
}
)
# Empty by policy: no internal tracker ID is sanctioned in shipped source on
# this public tree. An entry would be keyed by
# ``(relative_path_from_src_bonfire, line_number, expected_line_prefix)`` —
# but the set is intentionally empty, so every ``BON-NNN`` in source is an
# offender with no exception path.
_ALLOWLIST: frozenset[tuple[str, int, str]] = frozenset()


def _iter_python_files(root: Path) -> list[Path]:
Expand Down Expand Up @@ -127,11 +63,12 @@ def _is_allowlisted(rel_path: str, lineno: int, line: str) -> bool:


def test_no_bon_ref_in_src_outside_allowlist() -> None:
"""No ``BON-\\d+`` ref may live in ``src/bonfire/`` outside the allowlist.
"""No ``BON-\\d+`` ref may live in ``src/bonfire/``.

Failure message lists every offender as ``(path, line_no, line_text)``
so the Warrior can navigate directly. Future legitimate citations
require explicitly extending ``_ALLOWLIST`` above with rationale.
so a contributor can navigate directly. The fix is always to rewrite
the offending comment or docstring into neutral prose; the allowlist
stays empty.
"""
offenders: list[tuple[str, int, str]] = []
for path in _iter_python_files(_SRC_DIR):
Expand Down