From e043d41d846037aa91183fc2df19e21968aeb431 Mon Sep 17 00:00:00 2001
From: Ali Bahaloo
Date: Sun, 26 Jul 2026 23:05:10 -0700
Subject: [PATCH 1/4] docs(007): BRD-004, constitution v3.0.0, and spec for
decommissioning the FueliX AI analyzer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Planning artifacts for retiring the advisory-driven AI mode so the
inventory-driven Cisco PSIRT check becomes the tool's only behaviour.
- brds/004: business requirements (31 items) with the decisions settled
during review — options removed outright, product renamed, version 4.0.0
- constitution 2.2.1 -> 3.0.0 (MAJOR): removes every AI-provider rule.
Principle I drops FueliX from permitted network access; Principle IV drops
the AI-normalization clause and collapses to a single `Needs review` label;
Principle V replaces AI-endpoint minimization with a stricter Cisco-bounded
rule; the "AI provider" constraint and the dry-run quality gate are deleted.
Product renamed to Cisco Advisory Impact Analyzer (command, distribution,
and repository names unchanged).
- specs/007: 6 prioritized user stories, 38 functional requirements, 12
success criteria, 10 edge cases, and the four /speckit-clarify decisions.
No product code changes yet.
Co-Authored-By: Claude Opus 5
---
.specify/feature.json | 2 +-
.specify/memory/constitution.md | 185 +++++-----
brds/004-decommission-fuelix-ai-analyzer.md | 333 ++++++++++++++++++
.../checklists/requirements.md | 68 ++++
specs/007-decommission-ai-analyzer/spec.md | 258 ++++++++++++++
5 files changed, 765 insertions(+), 81 deletions(-)
create mode 100644 brds/004-decommission-fuelix-ai-analyzer.md
create mode 100644 specs/007-decommission-ai-analyzer/checklists/requirements.md
create mode 100644 specs/007-decommission-ai-analyzer/spec.md
diff --git a/.specify/feature.json b/.specify/feature.json
index f7e88b1..ff19045 100644
--- a/.specify/feature.json
+++ b/.specify/feature.json
@@ -1,3 +1,3 @@
{
- "feature_directory": "specs/006-api-inventory-check"
+ "feature_directory": "specs/007-decommission-ai-analyzer"
}
diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md
index e3232f6..8b89c22 100644
--- a/.specify/memory/constitution.md
+++ b/.specify/memory/constitution.md
@@ -1,30 +1,60 @@
-# Cisco Advisory Impact Agent Constitution
+# Cisco Advisory Impact Analyzer Constitution
## Core Principles
### I. Standard-Library-First & Minimal Dependencies
-All network access (HTTP to Cisco and to FueliX) and core logic MUST use the Python
-standard library. A third-party package MAY be added ONLY when the standard library
-cannot reasonably provide the capability, and each such dependency MUST be justified in
-the pull request that introduces it. The runtime dependency set is deliberately small
-(currently `openpyxl` for Excel and `python-dotenv` for `.env` loading); growing it is a
-reviewed decision, not a default.
+All network access (HTTP to Cisco) and core logic MUST use the Python standard library. A
+third-party package MAY be added ONLY when the standard library cannot reasonably provide the
+capability, and each such dependency MUST be justified in the pull request that introduces it.
+The runtime dependency set is deliberately small (currently `openpyxl` to read the user's
+inventory spreadsheet and `python-dotenv` for `.env` loading); growing it is a reviewed
+decision, not a default, and a dependency that outlives its purpose MUST be removed.
Rationale: A small, standard-library-based footprint keeps the tool portable, trivially
installable behind corporate proxies, and low in supply-chain attack surface — essential
@@ -80,51 +110,53 @@ gives every platform the same install/run/update experience with no manual envir
### III. CLI-First, Scriptable Interface
Every capability MUST be reachable through the command line. The tool MUST support both
-an interactive mode and a fully flag-driven mode (e.g. `--url`, `--inventory`,
-`--output-dir`, `--dry-run`) so it can be automated and tested without human input.
-Human-readable output goes to the user via the `ui` module; errors MUST be actionable
-(state what failed and how to fix it). Exit codes MUST be meaningful (non-zero on
-failure; `130` on interrupt).
+an interactive mode and a fully flag-driven mode (e.g. `--inventory`, `--sheet`,
+`--output-dir`) so it can be automated without human input. Human-readable output goes to the
+user via the `ui` module; errors MUST be actionable (state what failed and how to fix it).
+Exit codes MUST be meaningful (non-zero on failure; `130` on interrupt). An option that is
+withdrawn MUST be refused with a message naming it as removed and stating what to run instead,
+rather than falling through to a generic argument error.
Rationale: Scriptability enables automation and repeatable tests; clear text I/O keeps
-the tool debuggable and honest about what it did.
+the tool debuggable and honest about what it did. A withdrawn option is a promise the tool
+once made, so breaking it deserves an explanation rather than a shrug.
### IV. Conservative & Traceable Analysis (NON-NEGOTIABLE)
Impact determinations MUST NOT overstate risk. When the available data is insufficient
-to decide — for example when impact depends on runtime configuration the inventory does
-not track, or when the authoritative source does not recognize a device's software
-release or hardware platform — the result MUST be labelled as undecided, never guessed.
-The label MUST be unambiguous within the report it appears in; `Indeterminate` in the
-advisory-driven analysis and `Needs review` in the inventory-driven check are both
-conforming. A determination that a device is *not* affected MUST rest on positive
-confirmation from official Cisco source data, never on the mere absence of a result; an
-undecided device MUST NOT be presented as unaffected. Every determination MUST be
-traceable to official Cisco source data (an ERP page, an advisory's CSAF JSON, or Cisco's
-PSIRT advisory API); the tool MUST NOT invent advisory facts. Where a mode uses AI to
-reach a determination, that output MUST be normalized and validated before it reaches the
-report.
+to decide — for example when Cisco does not recognize a device's software release, does not
+list that release for the device's hardware platform, or cannot be reached — the result MUST be
+labelled `Needs review`, never guessed. A determination that a device is *not* affected MUST
+rest on positive confirmation from official Cisco source data, never on the mere absence of a
+result; a device labelled `Needs review` MUST NOT be presented as unaffected, and every such
+device MUST carry a human-readable reason. Every determination MUST be traceable to official
+Cisco source data — the PSIRT advisory API and the platform and release catalogues it publishes;
+the tool MUST NOT invent advisory facts, infer a verdict Cisco has not given, or derive one from
+any source other than Cisco's published data.
Rationale: People act on these reports to protect firewalls. A false "Not Affected" is
dangerous and a fabricated "Affected" erodes trust; conservative, source-anchored
-results are the only acceptable posture. Each mode may choose the clearest label for its
-own report, but the substance is fixed: an undecidable device is never presented as safe,
-and safety is asserted only when the source data affirms it. The positive-confirmation
-rule exists because an authoritative source can return "nothing found" both for a device
-that is genuinely clean and for one it cannot recognize at all — treating those alike
-would silently convert a data-quality problem into a clean bill of health.
+results are the only acceptable posture. The positive-confirmation rule exists because an
+authoritative source can return "nothing found" both for a device that is genuinely clean and
+for one it cannot recognize at all — treating those alike would silently convert a data-quality
+problem into a clean bill of health. Requiring the verdict to come from Cisco rather than from
+inference is what makes each finding reproducible and defensible to the person who has to act
+on it.
### V. Secrets Hygiene & Data Locality
API keys and other secrets MUST live only in the per-user configuration file (or in
environment variables) and MUST NEVER be committed, logged, or printed. The per-user
configuration file MUST be stored outside any working folder and, where the OS supports
it, restricted to owner-only permissions. The tool MUST refuse to run when a required
-secret is missing rather than proceeding insecurely. Inventory data MUST stay local; only
-the minimum advisory and inventory context required for a determination may be sent to the
-FueliX AI endpoint. `.gitignore` MUST continue to exclude secrets, inventory, and
-generated reports.
+secret is missing rather than proceeding insecurely, and MUST make no external request in that
+case. Inventory data MUST stay local: the only inventory-derived facts that may leave the
+machine are the product family, hardware platform, and software release being checked — never a
+firewall name, a priority, or any other identifying detail. `.gitignore` MUST continue to
+exclude secrets, inventory, and generated reports.
Rationale: The inventory describes an organization's security posture and the API key is
-a personal credential; leaking either is a direct harm.
+a personal credential; leaking either is a direct harm. Bounding the disclosure to three derived
+values means an observer of the tool's traffic learns what software is being asked about, but
+nothing about who runs it or where.
## Technology & Data Constraints
@@ -135,28 +167,20 @@ a personal credential; leaking either is a direct harm.
source checkout (into a virtual environment) is a supported fallback where `uv` is unavailable;
`--update` is uv-specific and does not apply on that path. The product version is single-sourced
from the committed `VERSION` file into the package metadata at build time.
-- Source of truth: Cisco Event Response (ERP) pages and official advisory CSAF JSON
- fetched from `sec.cloudapps.cisco.com`, and Cisco's PSIRT openVuln advisory API — the
- same published data that backs Cisco's public Software Checker — for release- and
- platform-scoped advisory lookups and for the platform/release catalogues those lookups
- are validated against. Catalogues MUST be retrieved from Cisco rather than hardcoded, so
- newly published releases and platforms are picked up without a tool release. No scraped
- or cached-as-authoritative alternatives.
-- AI provider: FueliX (OpenAI-compatible Chat Completions endpoint) accessed over the
- standard library, using the current default model configured in `fuelix.py`.
+- Source of truth: Cisco's PSIRT openVuln advisory API — the same published data that backs
+ Cisco's public Software Checker — for release- and platform-scoped advisory lookups and for the
+ platform/release catalogues those lookups are validated against. Catalogues MUST be retrieved
+ from Cisco rather than hardcoded, so newly published releases and platforms are picked up
+ without a tool release. No scraped or cached-as-authoritative alternatives.
- Inputs/outputs: the inventory is a user-provided `.xlsx` (sheet `FW_List`) located in the
- current working folder; reports are written into an `output/` folder inside that same
- working folder as timestamped files — `analysis_output_*.xlsx` for the advisory-driven
- analysis and `impact_report_*.html` for the inventory-driven check. An HTML report MUST be
- a single self-contained file that renders with no network access, so it survives being
- emailed or archived. Reports MUST never be overwritten. Header matching MUST stay tolerant
- of minor naming variation.
-- Networking: the analyzer itself contacts only `sec.cloudapps.cisco.com` (Cisco
- ERP/CSAF), `api.fuelix.ai` (AI), `apix.cisco.com` (Cisco PSIRT advisory data and
+ current working folder; the report is written into an `output/` folder inside that same
+ working folder as a timestamped `impact_report_*.html` file. The report MUST be a single
+ self-contained file that renders with no network access, so it survives being emailed or
+ archived. Reports MUST never be overwritten. Header matching MUST stay tolerant of minor
+ naming variation. Advisory content received from Cisco MUST be treated as untrusted text that
+ cannot alter the report's structure, styling, or behaviour.
+- Networking: the analyzer itself contacts only `apix.cisco.com` (Cisco PSIRT advisory data and
platform/release catalogues) and `id.cisco.com` (short-lived access token for that API).
- To the Cisco advisory API the tool MUST send only the derived product family, hardware
- platform and software release being checked — never a firewall name or any other inventory
- identifier — keeping that disclosure strictly smaller than what the AI endpoint receives.
Version and update checks additionally contact
GitHub — `api.github.com` and `github.com` — solely to discover the latest published
release; they send no inventory or secrets and MUST be best-effort and time-bounded so
@@ -181,12 +205,13 @@ constitutional compliance is enforced.
## Development Workflow & Quality Gates
-- Tests: extraction and parsing logic (CSAF product sections, affected releases,
- inventory matching) MUST have tests, and the existing test suite MUST pass before a
- change is merged. Changes to CSAF/ERP parsing or inventory matching MUST add or update
- tests covering the new behavior.
-- Dry-run: user-facing changes that affect analysis flow SHOULD be exercisable via
- `--dry-run` (no AI call, no network write) to keep verification cheap.
+- Tests: the logic behind every verdict — inventory loading and column matching, release
+ normalization, platform and release catalogue handling, verdict assignment, and report
+ generation — MUST have tests, and the existing test suite MUST pass before a change is merged.
+ Changes to any of that logic MUST add or update tests covering the new behavior. Because the
+ tool offers no offline preview and every run contacts Cisco, the test suite is the primary
+ means of verifying an analysis-flow change without credentials: such changes MUST be covered
+ by tests that run with no credentials and no network access.
- Review: pull requests MUST confirm compliance with the Core Principles, especially
Principle IV (conservative, traceable analysis) and Principle V (secrets/data
handling). Any new runtime dependency MUST be explicitly justified (Principle I).
@@ -195,7 +220,7 @@ constitutional compliance is enforced.
those flows change. Their instructions MUST remain consistent with each other and with
the code — including the working-folder inventory and `output/` conventions and where
files are read and written. A change to the instructions in one MUST be mirrored in the
- other within the same pull request.
+ other within the same pull request. Neither may describe a capability the tool does not have.
- Versioning & releases: the committed `VERSION` file is the single source of truth for the
product version and MUST follow semantic versioning. A release is cut by pushing a git tag
equal to the current `VERSION` and publishing a matching GitHub Release; the Release workflow
@@ -208,7 +233,7 @@ constitutional compliance is enforced.
## Governance
-This constitution supersedes ad-hoc practice for the Cisco Advisory Impact Agent. All
+This constitution supersedes ad-hoc practice for the Cisco Advisory Impact Analyzer. All
changes MUST comply with the Core Principles; when a change cannot, the reviewer MUST
require either a compliant alternative or an explicit, documented justification recorded
in the pull request.
@@ -223,4 +248,4 @@ Compliance is verified at review time. Dependent Spec Kit artifacts (plan, spec,
tasks templates and their generated documents) MUST remain consistent with the principles
above; the Constitution Check in the planning workflow enforces this on each feature.
-**Version**: 2.2.1 | **Ratified**: 2026-07-14 | **Last Amended**: 2026-07-25
+**Version**: 3.0.0 | **Ratified**: 2026-07-14 | **Last Amended**: 2026-07-26
diff --git a/brds/004-decommission-fuelix-ai-analyzer.md b/brds/004-decommission-fuelix-ai-analyzer.md
new file mode 100644
index 0000000..a9c30a9
--- /dev/null
+++ b/brds/004-decommission-fuelix-ai-analyzer.md
@@ -0,0 +1,333 @@
+# Business Requirements Document (BRD): Decommission the FueliX AI Analyzer — the Cisco API Check Becomes the Only Mode
+
+## Document Control
+
+| Field | Value |
+| ----------------- | ------------------------------------------------------------ |
+| BRD ID | BRD-004 |
+| Title | Decommission the FueliX AI Analyzer (`caia` becomes the inventory-driven Cisco API check) |
+| Author | Ali Bahaloo / TELUS Digital |
+| Status | Draft |
+| Version | 0.1.0 |
+| Created | 2026-07-26 |
+| Last updated | 2026-07-26 |
+| Related documents | Repo: https://github.com/xavient/caia-cli · BRD-003 (Inventory-Driven Vulnerability Check via the Cisco PSIRT API): `brds/003-api-inventory-check.md` · BRD-001 (Self-Update Mechanism): `brds/001-auto-updater.md` · BRD-002 (Self-Uninstall Command): `brds/002-uninstall.md` · Constitution: `.specify/memory/constitution.md` · Spec 006: `specs/006-api-inventory-check/spec.md` · Cisco PSIRT openVuln API: https://developer.cisco.com/docs/psirt/ |
+
+## 1. Executive Summary
+
+The tool ships two modes today: an advisory-driven analysis that asks a FueliX-hosted AI model to read one Cisco advisory and infer which firewalls it affects, and an inventory-driven check (`caia --api`, delivered by BRD-003) that asks Cisco's own PSIRT API, per device, which advisories apply. The second mode answers the more useful question, from the authoritative source, deterministically. This BRD retires the first one entirely. After this change there is exactly one mode: running `caia` checks the whole inventory against Cisco's published advisory data and writes the HTML impact report. The AI capability is removed root and branch — the FueliX client, the advisory-URL flow, the Excel report, the AI credential and its configuration prompts, every documentation claim of AI-driven or agentic analysis, and every constitutional rule that assumed an AI provider existed. The product is renamed **Cisco Advisory Impact Analyzer**, keeping the `caia` command and the `caia-cli` distribution unchanged, and the product version moves to **4.0.0** to mark the removal.
+
+## 2. Business Context & Problem Statement
+
+BRD-003 shipped a check that gets its verdicts from Cisco directly. That made the older AI-driven mode the weaker of the two on every axis that matters for a security decision, while the cost of keeping it is paid continuously.
+
+- **Inference where an authority exists.** The AI mode reads an advisory's "Vulnerable Products" prose and infers version overlap against the inventory. Cisco publishes the per-platform, per-release answer — and the release that fixes it. Keeping an inferential path alongside an authoritative one invites the wrong one to be used, and leaves two differently-derived answers to reconcile when they disagree.
+- **A per-person credential and a third-party dependency for no unique benefit.** Every user must obtain and configure a FueliX API key before the default command works at all. That is the first thing a new user hits, and it buys them a result the Cisco path produces without it.
+- **Inventory context leaves the organisation.** The AI mode sends advisory text plus the inventory's model/type/version combinations to an external AI endpoint. The Cisco path sends strictly less — a derived family, platform, and release, never a firewall name. Removing the AI mode removes the larger disclosure entirely.
+- **Double surface, permanently.** Two modes mean two credential sets, two report formats (`analysis_output_*.xlsx` and `impact_report_*.html`), two undecided labels (`Indeterminate` and `Needs review`), two documented setup paths, and a mode-comparison table a reader must work through before they can run anything. Roughly 25 FueliX references in `README.md` and 7 on the landing page exist only to serve the mode being retired.
+- **The description is now wrong.** The product is presented as "Cisco Advisory Impact **Agent**" with the tagline "Agentic firewall impact analysis" and an "AI impact analysis" feature card. Once the AI path is gone, that misdescribes the tool to every reader — and for a security tool, overstating how the answer is reached is a trust problem, not a marketing one.
+- **Governance is out of step.** Five separate clauses of the constitution — the standard-library principle, the conservative-analysis principle, the secrets/data-locality principle, the AI-provider and networking constraints, and the dry-run quality gate — are written around an AI provider. Until they are amended, the governing document and the product contradict each other, and every future feature's Constitution Check inherits that contradiction.
+
+The cost of inaction is a tool that is harder to adopt than it needs to be, describes itself inaccurately, discloses more inventory data than its own capability requires, and carries a maintenance tax on every document and test for a mode nobody should choose.
+
+## 3. Business Objectives & Goals
+
+- **G1** — Make the deterministic, Cisco-sourced check the tool's single behaviour, reachable by typing `caia` with nothing else.
+- **G2** — Remove the AI capability completely, so no run can contact an AI service, require an AI credential, or produce an AI-derived verdict.
+- **G3** — Reduce what a new user must obtain and configure before their first successful run to one credential set: Cisco PSIRT API access.
+- **G4** — Shrink the data the tool discloses externally to only what the Cisco check needs.
+- **G5** — Make every user-facing description — command, documentation, landing page, package metadata, product name — an accurate account of a deterministic tool.
+- **G6** — Bring the governing constitution back into agreement with the product, with no residual reference to an AI provider or an AI-derived determination.
+- **G7** — Preserve the inventory-driven check's behaviour exactly: same verdicts, same report, same reasons, same exit statuses. Only the way it is invoked changes.
+- **G8** — Leave no residue: no dead code, tests, sample files, automated checks, or documentation belonging to the retired mode.
+
+## 4. Stakeholders & Users
+
+| Stakeholder / user | Role in this product | What they need from it |
+| ------------------ | -------------------- | ---------------------- |
+| Security / network analyst | Primary user | One command that answers "what is my exposure?"; no AI key to obtain; no choice to make between two modes |
+| Existing user of the advisory-driven mode | Affected user | To be told plainly that the mode is gone and what replaces it, rather than meeting a confusing error |
+| Firewall / patching engineer | Downstream actor | The same HTML worklist they use today, unchanged by this removal |
+| Team lead / security manager | Reviewer, reporting | Confidence that every verdict now comes from Cisco's published data, and an accurate description of the tool to pass on |
+| IT / security review | Reviewer | Evidence that the external AI dependency and its data disclosure are gone, and that the credential surface has shrunk |
+| Maintainers | Owners | A single-mode codebase, documentation set, and constitution with no dual-mode caveats to keep in sync |
+| New user | Onboarding | A prerequisites list, install path, and first run with no AI service in it |
+
+## 5. Scope
+
+### 5.1 In Scope
+
+- Making the inventory-driven Cisco API check the behaviour of `caia` invoked with no mode flag.
+- Removing the advisory-driven analysis: the advisory/ERP URL flow, the AI assessment of advisory content, the downloading and retention of advisory CSAF files, and the Excel report it produced.
+- Removing every command-line option that existed only to serve that mode — the mode selector, the advisory URL, and the two options controlling retention of downloaded CSAF files — and refusing any removed option with a clear, actionable message rather than a generic argument error.
+- Removing the dry run entirely, including its preview of the checks that would be made, and removing the constitutional quality gate that requires user-facing changes to be exercisable through it. Every invocation therefore requires Cisco credentials and contacts Cisco.
+- Correcting the description of every option that survives, so none of them describes the retired mode's behaviour.
+- Removing the AI service credential and its related settings from configuration entirely: no prompt, no read, no honouring of an existing stored or environment value.
+- Ensuring `caia --config` collects only the Cisco client identifier and client secret, preserving its existing verification behaviour.
+- Renaming the product to **Cisco Advisory Impact Analyzer**, keeping the `caia` command, the `caia-cli` distribution name, and the repository name unchanged.
+- Correcting every user-facing description — the startup banner's product name and slogan, the command help and its option descriptions, the interactive configuration screen, the message shown after uninstalling, the comments written into the per-user configuration file, `README.md`, `docs/index.html`, package metadata, and the sample environment file — to describe a deterministic, Cisco-data-driven tool with no AI claim.
+- Adopting one canonical tagline and one canonical command description (given verbatim in BR-17) and using each one everywhere it appears, so the four copies of today's tagline cannot drift apart again.
+- Redefining what counts as an unattended or flag-driven run wherever that was previously inferred from the presence of the advisory URL.
+- Amending the constitution so that no principle, constraint, or quality gate refers to an AI provider, an AI-derived determination, the AI endpoint, or the Excel report.
+- Removing the retired mode's tests, sample values, and clean-room install-test steps, and confirming the surviving suite passes.
+- Bumping the product version to 4.0.0 and cutting the release through the project's existing release process.
+
+### 5.2 Out of Scope
+
+- Any change to how the inventory-driven check reaches its verdicts — matching, catalogues, quotas, verdict rules, reasons, or report content. That behaviour is fixed by BRD-003 and must survive untouched.
+- Any new capability, output format, or flag. This is a removal, not a redesign.
+- Re-introducing AI for the judgement Cisco's data cannot make (whether a vulnerable feature is enabled). Explicitly not replaced by anything.
+- Migration tooling for users of the retired mode. User communication is handled directly by the product owner; the product's only obligation is a clear message on a retired flag.
+- Renaming the `caia` command, the `caia-cli` distribution, or the GitHub repository. Only the product's descriptive name changes.
+- Rewriting historical specs and BRDs (`specs/001`–`006`, `brds/001`–`003`). They are a record of decisions made at the time and stay as written.
+- Removing the ability to read a working-folder `.env` for automation; only its AI variables go away.
+- Changing the inventory format, its discovery, or the `output/` folder conventions.
+
+## 6. User Journeys *(feeds the spec's prioritized user stories)*
+
+### Journey 1 — One command, one answer (Priority: P1)
+
+- **Actor:** Security / network analyst with their inventory in the working folder and Cisco credentials configured.
+- **Trigger:** They run `caia`.
+- **Outcome / value:** The fleet-wide impact report, with no mode to choose, no URL to supply, and no AI key to have obtained first.
+- **Flow:**
+ 1. The analyst runs `caia` in the folder holding their inventory.
+ 2. The tool loads the inventory and reports how many devices it will assess.
+ 3. It asks Cisco which advisories apply to each distinct family / platform / release combination.
+ 4. It assigns every device Impacted, Not impacted, or Needs review.
+ 5. It writes a timestamped HTML report to the output folder and prints its location.
+- **Acceptance:**
+ - **Given** a valid inventory and configured Cisco credentials, **When** the analyst runs `caia` with no other flags, **Then** the inventory-driven check runs and a new timestamped HTML report is written and its path reported.
+ - **Given** the same inventory and credentials, **When** the run completes, **Then** its verdicts, reasons, report content, and exit status are identical to what `caia --api` produced before this change.
+ - **Given** any run, **When** it completes, **Then** no Excel report is produced, no advisory URL was requested or accepted, and no advisory CSAF file or temporary working folder was created.
+ - **Given** a run, **When** its network activity is observed, **Then** it contacts only Cisco's advisory and token services and — unless suppressed — GitHub for the version check, and no AI service.
+ - **Given** any option the product retains is supplied, **When** the tool runs, **Then** it behaves exactly as it does today for the inventory-driven check, and its description in the help matches what it actually does.
+ - **Given** an interactive run on a version that is out of date, **When** the tool starts, **Then** the update offer behaves per a stated rule that no longer depends on an advisory URL having been supplied.
+
+### Journey 2 — Set up with one credential set (Priority: P1)
+
+- **Actor:** New user, or an existing user reconfiguring.
+- **Trigger:** They run `caia --config`.
+- **Outcome / value:** Setup asks only for the Cisco client identifier and secret. Nothing prompts for an AI key, and nothing about a previously-stored AI key affects their runs.
+- **Flow:**
+ 1. The user runs `caia --config`.
+ 2. The tool prompts for the Cisco client identifier (visible) and the Cisco client secret (not echoed, with the existing capture confirmation).
+ 3. It verifies the credentials with Cisco, distinguishing a rejection from an unreachable service exactly as it does today.
+ 4. It saves the settings it still manages to the per-user configuration and reports where.
+- **Acceptance:**
+ - **Given** a fresh machine, **When** the user runs `caia --config`, **Then** they are prompted only for the Cisco client identifier and client secret, and never for an AI key, model, or endpoint.
+ - **Given** a per-user configuration containing AI settings from an earlier version, **When** any command runs, **Then** those values are ignored and change nothing about the run.
+ - **Given** that same configuration, **When** `caia --config` saves, **Then** the written file contains only the settings the product still manages, with its owner-only permissions and hand-editable format preserved.
+ - **Given** an AI-related environment variable is set, **When** any command runs, **Then** it has no effect and no message references it.
+ - **Given** Cisco rejects the credentials, or cannot be reached, **When** `caia --config` completes, **Then** it behaves exactly as it does today for each case.
+ - **Given** a user who has never configured an AI key, **When** they run `caia`, **Then** the run proceeds on Cisco credentials alone and never refuses for a missing AI key.
+ - **Given** the per-user configuration file is opened by hand, **When** its header comments are read, **Then** they document only the settings and environment overrides the product still honours, under the product's current name.
+ - **Given** the user uninstalls the product, **When** it reports which saved settings were left behind, **Then** it names only credentials the product actually stores.
+ - **Given** a non-interactive session, **When** the product is uninstalled with the unattended-confirmation option, **Then** it completes without a prompt exactly as it does today, and the option's description names the uninstall confirmation as the prompt it skips.
+
+### Journey 3 — A retired option says so (Priority: P2)
+
+- **Actor:** An existing user, or a script, still passing an option that belonged to the retired mode.
+- **Trigger:** They run the tool with the mode selector, an advisory URL, or one of the CSAF-retention options.
+- **Outcome / value:** An immediate, self-explanatory refusal — never a silent fall-through to a different behaviour, and never a bare "unrecognized argument" that leaves them guessing.
+- **Flow:**
+ 1. The user runs the tool with a removed option.
+ 2. The tool refuses before doing any work, naming the option as removed.
+ 3. It states what the tool now does instead — `caia` checks the whole inventory against Cisco's published data.
+ 4. It exits non-zero.
+- **Acceptance:**
+ - **Given** the former mode selector is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed and stating that the product now performs that check by default, and no analysis runs.
+ - **Given** the former advisory-URL option is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed and stating that the advisory-driven analysis no longer exists, and nothing is written and no advisory is fetched.
+ - **Given** either CSAF-retention option is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed and stating that the product no longer downloads advisory files.
+ - **Given** the dry-run option is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed, and no preview of the checks that would be made is produced.
+ - **Given** any removed option is supplied, **When** the tool refuses, **Then** the message is distinguishable from a generic unknown-argument error and tells the user what to run instead.
+ - **Given** the help is displayed, **When** the options are listed, **Then** no removed option appears, and no listed option's description refers to the retired mode.
+
+### Journey 4 — The tool describes itself accurately (Priority: P2)
+
+- **Actor:** A prospective or new user reading the landing page or README before installing.
+- **Trigger:** They open `docs/index.html` or `README.md`, or run `caia --help`.
+- **Outcome / value:** They learn what the tool actually is — a deterministic checker that reports Cisco's own published verdicts — under a name that does not imply AI, and a prerequisites list containing no AI service.
+- **Flow:**
+ 1. The reader sees the product name and one-line description.
+ 2. They read the prerequisites and obtain Cisco PSIRT API credentials.
+ 3. They install, run `caia --config`, then `caia`.
+ 4. Nothing they read mentions an AI provider, an AI key, a second mode, or an Excel report.
+- **Acceptance:**
+ - **Given** any user-facing text — product name, command help, README, landing page, package metadata, sample environment file — **When** it is read, **Then** it contains no claim of AI-driven or agentic analysis and no reference to an AI provider.
+ - **Given** the prerequisites, **When** a new user follows them, **Then** the only credential they are told to obtain is Cisco PSIRT API access.
+ - **Given** the README and the landing page, **When** they are compared, **Then** their install, configure, run, and update instructions agree with each other and with the code.
+ - **Given** the product name, **When** it appears anywhere, **Then** it is the agreed non-AI name and the `caia` command and `caia-cli` distribution are unchanged.
+ - **Given** a returning user reads the documentation, **When** they look for the advisory-URL mode, **Then** they find a plain statement that it was removed and what replaces it.
+
+### Journey 5 — Governance matches the product (Priority: P2)
+
+- **Actor:** Maintainer or reviewer planning the next feature.
+- **Trigger:** A feature enters `/speckit-plan` and its Constitution Check runs.
+- **Outcome / value:** The constitution describes the tool that exists, so the check is meaningful rather than something to explain away.
+- **Flow:**
+ 1. The constitution is amended to remove every AI-provider assumption.
+ 2. Its Sync Impact Report records what changed and why, and the version is bumped as a MAJOR amendment.
+ 3. The next feature's Constitution Check runs against rules that hold.
+- **Acceptance:**
+ - **Given** the amended constitution, **When** it is searched, **Then** no principle, constraint, or gate references an AI provider, an AI-derived determination, the AI endpoint, or the Excel report.
+ - **Given** the conservative-analysis principle, **When** it is read after amendment, **Then** it still forbids presenting an undecidable device as safe and still requires positive confirmation from Cisco data before "not impacted", with a single undecided label.
+ - **Given** the recorded network destinations, **When** they are read, **Then** they list only the destinations the tool still contacts.
+ - **Given** the amendment, **When** it is committed, **Then** it carries an updated Sync Impact Report and a MAJOR version bump, and any dependent templates are confirmed aligned.
+
+### Journey 6 — Nothing left behind (Priority: P3)
+
+- **Actor:** Maintainer reviewing the change, and CI.
+- **Trigger:** The pull request is opened.
+- **Outcome / value:** The removal is complete, not partial — no dormant code that could be re-enabled by accident, no tests exercising a capability that no longer exists, no dependency retained for a reason that has gone.
+- **Flow:**
+ 1. Every component belonging solely to the retired mode is deleted.
+ 2. Tests covering only the retired mode are removed; tests covering shared behaviour are retained and still pass.
+ 3. Sample files and the clean-room install test exercise only the surviving flows.
+ 4. Each remaining runtime dependency is confirmed still necessary.
+- **Acceptance:**
+ - **Given** the merged change, **When** the codebase is searched for the AI provider's name, **Then** it appears nowhere outside the historical `specs/` and `brds/` records.
+ - **Given** the test suite, **When** it runs, **Then** it passes with no test referencing the retired mode.
+ - **Given** the clean-room install test, **When** it runs end to end, **Then** every step it performs applies to the surviving tool.
+ - **Given** the declared runtime dependencies, **When** they are reviewed, **Then** each is still required by surviving behaviour and any that is not has been removed.
+ - **Given** the released version, **When** it is inspected, **Then** it is 4.0.0 and the tag, the committed version, and the version the installed tool reports agree.
+
+## 7. Business Requirements
+
+| ID | Requirement | Priority |
+| ----- | ------------------------------------ | -------- |
+| BR-01 | Invoked with no mode flag, the product MUST perform the inventory-driven check against Cisco's published advisory data and write the HTML impact report — the behaviour previously reached only via `--api`. | P1 |
+| BR-02 | The product MUST NOT offer any advisory-URL or Event-Response-driven analysis; that capability is removed, not hidden or deprecated. It MUST NOT download, retain, or create temporary storage for advisory CSAF files. | P1 |
+| BR-03 | The product MUST NOT contact any AI service, and MUST NOT require, prompt for, read, or honour an AI service credential from any source — per-user configuration, environment variable, or working-folder `.env`. | P1 |
+| BR-04 | The product MUST NOT produce a spreadsheet report; the single output of a run is the timestamped, self-contained HTML impact report. | P1 |
+| BR-05 | The verdicts, reasons, report content, progress output, and exit statuses of the inventory-driven check MUST be unchanged by this decommission; only the way it is invoked changes. | P1 |
+| BR-06 | Every option belonging to the retired mode MUST be removed and MUST NOT appear in the product's help: the mode selector (`--api`), the advisory URL (`--url`), the two CSAF-retention options (`--keep-temp`, `--no-keep-temp`), and the dry run (`--dry-run`). | P1 |
+| BR-07 | The dry-run capability MUST be removed outright rather than restated for the surviving check: the product MUST NOT offer any way to preview the checks it would make without performing them. Every invocation consequently requires Cisco credentials and contacts Cisco, and the product MUST continue to refuse clearly and before any request when those credentials are absent. | P1 |
+| BR-08 | Supplying a removed option MUST cause the product to refuse before performing any work, exit non-zero, name the option as removed, and state what the product does instead — distinguishably from a generic unknown-argument error. | P1 |
+| BR-09 | Every option the product retains — including those selecting the inventory, its sheet and the output folder, and those covering unattended confirmation, configuration, version, update, uninstall, and update-check suppression — MUST keep its current behaviour, and every retained option's description MUST state what that option actually does under the surviving check, with no reference to an AI call, an advisory URL, or downloaded advisory files. | P1 |
+| BR-10 | The unattended-confirmation option (`--yes`, `-y`) MUST be retained with its behaviour intact, so uninstalling without a prompt in a non-interactive session remains possible as defined by BRD-002. Its description MUST be narrowed to state that it skips the uninstall confirmation, which is the only prompt it governs once the retired mode's confirmation is gone. | P1 |
+| BR-11 | Where the product previously inferred that a run was flag-driven or unattended from the presence of an advisory URL, it MUST adopt a stated replacement rule, and that rule MUST be documented in the option's description where it is user-visible. | P2 |
+| BR-12 | The interactive configuration command MUST prompt only for the Cisco client identifier and the Cisco client secret, and MUST NOT prompt for an AI key, model, or endpoint. | P1 |
+| BR-13 | The interactive configuration command MUST preserve its existing behaviour for Cisco credentials: visible identifier prompt, non-echoing secret prompt with capture confirmation, skippable prompts, retention of an existing value on empty input, verification with Cisco before saving, and the distinction between a rejection and an unreachable service. | P1 |
+| BR-14 | When configuration is saved, the resulting per-user configuration file MUST contain only settings the product still manages, so obsolete AI settings do not persist; the file MUST retain its owner-only protection and remain hand-editable, and encountering obsolete values MUST NOT produce an error. | P2 |
+| BR-15 | The product MUST be presented under a name that does not imply AI or agentic behaviour, while the `caia` command, the `caia-cli` distribution name, and the repository name remain unchanged. | P1 |
+| BR-16 | Every user-facing description — product name, command help, README, landing page, package metadata, sample environment file, and in-product text — MUST describe the product as producing deterministic verdicts from Cisco's published advisory data, and MUST contain no claim of AI-driven or agentic analysis and no reference to an AI provider. | P1 |
+| BR-17 | The product's tagline and its one-line command description MUST be the agreed copy below, and each MUST be used verbatim wherever it appears so the two strings cannot drift apart again. Tagline: **Fleet-wide firewall impact analysis, straight from Cisco** — the single canonical string for the startup banner, the package metadata, the README tagline, and the landing page, carrying the ` · TELUS Digital` attribution on the surfaces that already include it. One-line description: **Checks every firewall in your inventory against Cisco's published advisory data, and writes an HTML impact report.** Neither string may characterise the result as a binary affected/unaffected answer, since the product returns three verdicts. | P1 |
+| BR-18 | Documentation MUST list Cisco PSIRT API credentials as the only credential a user obtains, and MUST NOT list an AI service key among the prerequisites, the configuration steps, or the troubleshooting guidance. | P1 |
+| BR-19 | The README and the landing page MUST remain consistent with each other and with the product — one mode, one report, one credential set, and the current set of network destinations — within the same change. | P1 |
+| BR-20 | Documentation MUST state plainly that the advisory-URL mode has been removed and what a returning user should run instead. | P2 |
+| BR-21 | The project constitution MUST be amended so that no principle, constraint, or quality gate references an AI provider, an AI-derived determination, the AI endpoint, or the spreadsheet report. The quality gate requiring user-facing changes to be exercisable through a dry run MUST be deleted rather than reworded, since the capability it depends on is removed, and any illustrative list of command-line options in the principles MUST cite only options that still exist. | P1 |
+| BR-22 | The amended constitution MUST retain the conservative-analysis principle in substance: an undecidable device MUST never be presented as safe, "not impacted" MUST rest on positive confirmation from Cisco's published data, every determination MUST be traceable to that data, and a single unambiguous undecided label MUST be used. | P1 |
+| BR-23 | The amended constitution MUST record only the network destinations the product still contacts, and MUST retain the rule that the product sends Cisco only the derived product family, hardware platform, and software release — never a firewall name or other inventory identifier. | P1 |
+| BR-24 | The constitutional amendment MUST carry an updated Sync Impact Report, MUST bump the constitution's own version as a MAJOR amendment, and MUST confirm the dependent Spec Kit templates remain aligned. | P1 |
+| BR-25 | No component, sample file, automated test, or clean-room install check may retain a dependency on the removed capability, and the surviving test suite MUST pass. | P1 |
+| BR-26 | A completed run MUST demonstrably contact only Cisco's advisory and token services and — unless suppressed — the version-check service, with no request to any AI endpoint. | P1 |
+| BR-27 | The clean-room install test MUST exercise only surviving flows, including a run invoked with no mode flag and a refusal when credentials are absent. | P2 |
+| BR-28 | The sample environment file MUST document only the variables the product still honours. | P2 |
+| BR-29 | Each declared runtime dependency MUST be confirmed still required by surviving behaviour, and any dependency retained solely for the removed capability MUST be removed. | P2 |
+| BR-30 | The product version MUST be raised to 4.0.0, reflecting the removal of documented options and a documented mode, and released through the project's existing tag-and-release process. | P1 |
+| BR-31 | Historical specification and BRD records MUST be left as written; correcting the product's description MUST NOT involve rewriting the record of past decisions. | P3 |
+
+## 8. Success Metrics & Measurable Outcomes
+
+- **SC-01** — A user with Cisco credentials configured obtains a complete impact report by typing one command with no flags.
+- **SC-02** — A new user reaches their first successful run having obtained exactly one credential set.
+- **SC-03** — 100% of runs produce verdicts, reasons, and report content identical to what the previous `--api` mode produced for the same inventory and credentials.
+- **SC-04** — 0 requests to any AI endpoint across any run, in any mode of invocation, including with AI environment variables set.
+- **SC-05** — 0 occurrences of the AI provider's name in the product, its documentation, its samples, its tests, and its governing constitution, outside the historical `specs/` and `brds/` records.
+- **SC-06** — 100% of invocations using a removed option are refused before any work is done, with a message that names the option and states what to run instead.
+- **SC-07** — A user whose stored configuration or environment still carries AI settings sees no change in behaviour and no error attributable to them.
+- **SC-08** — 0 references to an AI service key remain in the prerequisites, configuration steps, or troubleshooting guidance of either the README or the landing page, and their instructions agree with each other and with the product.
+- **SC-09** — The constitution's Constitution Check can be answered affirmatively for the product as shipped, with no gate requiring an exception or explanation.
+- **SC-10** — The test suite passes with no test exercising the removed capability, and the clean-room install test completes end to end.
+- **SC-11** — The released version is 4.0.0, and the committed version, the git tag, and the version the installed tool reports all agree.
+- **SC-12** — The volume of user-facing documentation needed to explain how to run the tool is measurably smaller, with no mode-comparison decision left for the reader to make.
+
+## 9. Assumptions
+
+- Users of the retired advisory-driven mode are known to the product owner and will be told directly; the product's only obligation to them is a clear refusal message and an accurate document.
+- Nobody depends on the spreadsheet report as a machine-readable artifact for downstream tooling; the HTML report is the accepted output.
+- The inventory-driven check as delivered by BRD-003 is fit for purpose as the tool's sole behaviour, and its known coverage limits (per-family advisory publication cut-offs; verdicts that do not account for whether a vulnerable feature is enabled) remain acceptable and disclosed.
+- Reading the inventory spreadsheet remains necessary, so the spreadsheet-reading dependency stays even though the spreadsheet-writing capability goes.
+- Working-folder `.env` support stays for automation convenience; only its AI variables disappear.
+- Obsolete settings left in an existing per-user configuration file are harmless once nothing reads them, so no migration step or cleanup command is required — the next save simply stops carrying them forward.
+- Retaining `caia`, `caia-cli`, and the repository name means no user's install, PATH, scripts, or bookmarks break on the rename; only the descriptive product name changes.
+- The GitHub repository description and any external listing of the product are updated by the product owner outside this change.
+- Removing a documented mode and two documented options is a breaking change under semantic versioning, making 4.0.0 the correct product version.
+- Removing an AI-provider assumption from a principle, and removing a named constraint, is a backward-incompatible redefinition under the constitution's own amendment rules, making a MAJOR constitutional bump correct.
+
+## 10. Constraints
+
+- The inventory-driven check's behaviour is fixed: this change may not alter its verdicts, reasons, report, or exit statuses. Any observable difference is a defect, not an improvement.
+- The conservative-analysis posture is non-negotiable and survives the removal intact: no undecidable device may ever be presented as safe.
+- Credential handling must keep its current posture — per-user storage outside any working folder, owner-only where the OS supports it, never printed, logged, or written into a report or filename.
+- The product must behave identically on macOS, Windows, and Linux.
+- The minimal-dependency posture applies in reverse here: a dependency that survives must still be justified by surviving behaviour.
+- Documentation consistency is a merge gate: the README and the landing page must be corrected in the same change as the code.
+- The constitutional amendment must be in place before the implementation's Constitution Check can pass, so it precedes or accompanies planning rather than following implementation.
+- The change must reach `main` through a pull request from its own spec branch, per the project's branching rules.
+- Reports written by earlier versions must remain readable and must not be touched.
+
+## 11. Dependencies
+
+- **Input** — The constitutional amendment (`/speckit-constitution`), which must land before `/speckit-plan`'s Constitution Check can be answered honestly. This is the one ordering dependency in the change.
+- **Input** — BRD-003 and spec 006, which define the behaviour being promoted to the product's only behaviour; this BRD inherits their requirements rather than restating them.
+- **Input** — Cisco's PSIRT advisory service and its authentication service, now the product's only source of verdicts and its only analysis-time external dependency.
+- **Input** — The user's Cisco API credentials, captured through the interactive configuration command.
+- **Output** — The corrected `README.md` and `docs/index.html`, which the Documentation quality gate requires to stay mutually consistent.
+- **Output** — A 4.0.0 release cut through the existing tag-and-release process.
+- **Related** — The version-check and self-update capability (BRD-001) and the self-uninstall capability (BRD-002), which are untouched but share the command surface and must keep working after the parser changes.
+- **Related** — The clean-room install test, which currently exercises the retired mode and must be reduced to surviving flows.
+- **External** — The product owner's direct communication to existing users of the retired mode, and any update to the repository's public description.
+
+## 12. Risks & Mitigations
+
+| Risk | Impact | Likelihood | Mitigation |
+| ------ | ------ | ---------- | ---------- |
+| Removing the shared parser and configuration plumbing changes the surviving check's behaviour without anyone noticing | H | M | Treat behavioural equivalence as an explicit acceptance criterion (BR-05, SC-03); keep the existing inventory-driven test suite as the regression guard and require it to pass unmodified in substance |
+| An existing script runs `caia --api` and, on the new version, silently does something different or fails opaquely | M | H | Refuse removed options explicitly with a named, actionable message rather than relying on a generic argument error (BR-08, SC-06) |
+| A partial removal leaves dormant AI code or configuration that can be re-entered by an unusual invocation | H | L | Delete rather than disable; assert absence by search across product, docs, samples, and tests (BR-25, SC-05) |
+| Stored or environment AI settings on existing machines cause an error, a stale prompt, or unexpected behaviour after upgrade | M | M | Ignore obsolete values entirely and stop carrying them forward on the next save; never error on their presence (BR-03, BR-14, SC-07) |
+| The constitution is amended too late, so the implementation is planned and reviewed against rules the product no longer satisfies | M | M | Sequence the amendment as a prerequisite of planning and record it as the change's only ordering dependency (BR-21, Section 11) |
+| The conservative-analysis principle is weakened while editing the AI wording out of it | H | L | State its substance as a requirement in its own right so the amendment is checked against it, not just for what it removes (BR-22) |
+| Documentation is corrected in one place but not the other, leaving contradictory instructions | M | M | Existing Documentation gate applies: README and landing page corrected in the same change and checked against each other (BR-19) |
+| The rename ripples further than intended and breaks an install path, a script, or a link | M | L | Change only the descriptive product name; explicitly hold the command, distribution name, and repository name fixed (BR-15) |
+| A user upgrades expecting the advisory-URL mode for an advisory older than Cisco's data cut-off and finds no substitute | M | M | Accepted and deliberate: state the removal and Cisco's coverage limits plainly so the gap is visible rather than discovered mid-incident (BR-20, and the report's existing coverage disclosure) |
+| A dependency is dropped as "AI-only" when surviving behaviour still needs it, breaking installs | M | L | Require each dependency to be re-justified against surviving behaviour rather than removed by association (BR-29) |
+| The clean-room install test is left asserting the retired mode and fails or, worse, is quietly skipped | L | M | Reduce it to surviving flows as part of the same change and run it end to end (BR-27, SC-10) |
+| Removing the dry run leaves no way to exercise the product end to end without Cisco credentials, narrowing what the clean-room install test and any offline check can prove | M | H | Accepted as a deliberate consequence of the removal (BR-07): the credential-refusal path, inventory loading and its errors, and the unit test suite remain the credential-free coverage, and the install test is reduced to those |
+| A user validating a new or corrected inventory has no preview, so they spend Cisco request quota on a run made only to test the file | L | M | Accepted: a malformed or undiscoverable inventory still fails before any request is made, and repeated checks of a valid inventory collapse to the same small set of requests |
+
+## 13. Open Questions
+
+- What replaces "an advisory URL was supplied" as the test for a flag-driven or unattended run, which currently governs whether the start-of-run update offer appears? (Leaning: rely on the interactive-terminal check alone.)
+- Should the refusal for a removed option use the product's existing argument-error exit status, or a distinct status so automation can tell "you used a retired option" from "you mistyped an option"?
+- Should the obsolete settings in an existing per-user configuration file be dropped only when the user next saves configuration, or actively pruned on first run of the new version? (Leaning: only on save — the file is the user's, and a silent rewrite on an ordinary run is surprising.)
+- Where should the "this mode was removed, run `caia` instead" note live so a returning user actually meets it — the README, the release notes, or both?
+- Now that only one mode remains, is working-folder `.env` support still earning its dependency, or should automation rely on environment variables alone?
+- The report's own heading is already "Cisco Advisory Impact Report" — no AI claim and no "Agent" — so it needs no change. Confirm that it stays as written rather than tracking the product's new name.
+
+_Resolved during BRD review:_ the product is renamed **Cisco Advisory Impact Analyzer**, retaining `caia`, `caia-cli`, and the repository name (BR-15); the mode selector, the advisory URL, both CSAF-retention options, and the dry run are **removed outright** rather than kept as deprecated aliases (BR-06); the dry run is removed as a **capability**, not merely renamed, taking the constitution's dry-run quality gate with it and accepting that every invocation now needs credentials and contacts Cisco (BR-07, BR-21); the unattended-confirmation option is **kept** — it belongs to BRD-002's uninstall, not to the retired mode — with its description narrowed to the uninstall confirmation it actually governs (BR-10); AI settings are **no longer read from any source** and configuration **no longer prompts for them**, with obsolete values simply not carried forward on the next save (BR-03, BR-12, BR-14); the **constitution is amended in scope of this change** so that no rule references an AI provider (BR-21); the product version moves to **4.0.0** (BR-30); and the tagline and one-line description are settled as the agreed copy in BR-17, deliberately framed inventory-first and around Cisco as the source of truth, since the product's framing flipped from "given this advisory, which firewalls?" to "given my fleet, what does Cisco list against it?" (BR-17).
+
+## 14. Glossary
+
+| Term | Definition |
+| ---- | ---------- |
+| `caia` | The command the product installs on the user's PATH; unchanged by this BRD. After this change, running it with no mode flag performs the inventory-driven check. |
+| `caia-cli` | The distribution name the product is installed from; unchanged by this BRD. |
+| Cisco Advisory Impact Analyzer | The product's name after this change, replacing "Cisco Advisory Impact Agent" so the name no longer implies AI or agentic behaviour. |
+| Advisory-driven mode | The behaviour being retired: given an advisory or Event Response link, an AI model read the advisory and inferred which inventory firewalls were affected, producing a spreadsheet report. |
+| Inventory-driven check | The behaviour delivered by BRD-003 and, after this change, the product's only behaviour: every inventory device is checked against Cisco's published advisory data and given a verdict. |
+| AI service credential | The per-user key the retired mode required to reach its AI provider. Removed entirely: no longer prompted for, stored, read, or honoured. |
+| Per-user configuration | The stored credentials and settings kept in an OS-appropriate per-user location outside any working folder, so a run from any folder finds them. |
+| Impacted / Not impacted / Needs review | The three verdicts the surviving check assigns, unchanged by this BRD. "Needs review" is the single undecided label after the retired mode's "Indeterminate" disappears with it. |
+| Impact report | The timestamped, self-contained HTML file the surviving check writes into the output folder — after this change, the product's only report. |
+| Spreadsheet report | The timestamped Excel file the retired mode produced. Removed. |
+| CSAF file | The machine-readable form of a Cisco advisory that the retired mode downloaded, saved to a temporary folder, and fed to the AI model. The surviving check never downloads one, so the options that controlled retaining or deleting them go with the mode. |
+| Dry run | An invocation that resolved every inventory device to the question that would be asked of Cisco and reported it, without contacting Cisco and without writing a report. Removed by this BRD along with the constitutional quality gate built on it; after this change every invocation requires credentials and contacts Cisco. |
+| Startup banner | The splash printed on an interactive terminal, carrying the wordmark, the product name, and the slogan. Both the name and the slogan currently assert agentic AI analysis. |
+| Flag-driven run | A run whose inputs are all supplied as options rather than prompted for. The product currently infers this from the presence of an advisory URL, which disappears with the retired mode. |
+| Constitution | The project's governing document, whose principles and gates every feature is checked against during planning. Amended by this change. |
+| Sync Impact Report | The record at the head of the constitution describing what a given amendment changed, why, and how the version was bumped. |
+| Constitution Check | The gate in the planning workflow that verifies a proposed feature complies with the constitution's principles. |
+| Breaking change | A change that removes or alters documented behaviour users may depend on — here, the removal of a mode and two options — requiring a major version increment. |
diff --git a/specs/007-decommission-ai-analyzer/checklists/requirements.md b/specs/007-decommission-ai-analyzer/checklists/requirements.md
new file mode 100644
index 0000000..3f2b521
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/checklists/requirements.md
@@ -0,0 +1,68 @@
+# Specification Quality Checklist: Decommission the FueliX AI Analyzer
+
+**Purpose**: Validate specification completeness and quality before proceeding to planning
+**Created**: 2026-07-26
+**Feature**: [spec.md](../spec.md)
+
+## Content Quality
+
+- [x] No implementation details (languages, frameworks, APIs)
+- [x] Focused on user value and business needs
+- [x] Written for non-technical stakeholders
+- [x] All mandatory sections completed
+
+## Requirement Completeness
+
+- [x] No [NEEDS CLARIFICATION] markers remain
+- [x] Requirements are testable and unambiguous
+- [x] Success criteria are measurable
+- [x] Success criteria are technology-agnostic (no implementation details)
+- [x] All acceptance scenarios are defined
+- [x] Edge cases are identified
+- [x] Scope is clearly bounded
+- [x] Dependencies and assumptions identified
+
+## Feature Readiness
+
+- [x] All functional requirements have clear acceptance criteria
+- [x] User scenarios cover primary flows
+- [x] Feature meets measurable outcomes defined in Success Criteria
+- [x] No implementation details leak into specification
+
+## Notes
+
+- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`
+
+### Validation record (iteration 1)
+
+Resolved during drafting rather than deferred to `/speckit-clarify`:
+
+- **Zero [NEEDS CLARIFICATION] markers.** BRD-004 carried six open questions. Each had a reasonable
+ default available — five were resolved or leaned in the BRD itself, and `.env` support was already
+ settled as out of scope in BRD-004 §5.2. All six are recorded in the Assumptions section as
+ challengeable defaults rather than left as blocking markers.
+- **Named option strings retained deliberately.** `--api`, `--url`, `--keep-temp`, `--no-keep-temp`,
+ `--dry-run`, and `--yes` appear in FR-008 and FR-014. These are the product's user-facing surface,
+ not implementation detail: a removal spec that does not name what is removed is untestable.
+- **Named copy strings retained deliberately.** FR-023 fixes the tagline and one-line description
+ verbatim. These are user-visible product copy agreed during BRD review; paraphrasing them would
+ lose the decision.
+- **Success criteria are outcome-shaped.** SC-004, SC-005, and SC-006 count observable facts
+ (requests made, occurrences of a provider name, invocations refused) rather than naming any
+ component, module, or technology.
+- **FR-006 is the load-bearing regression requirement.** The surviving check shares its plumbing
+ with the mode being deleted, so behavioural equivalence is stated as a requirement and measured by
+ SC-003 rather than assumed.
+
+### Validation record (iteration 2 — after `/speckit-clarify`)
+
+Four clarifications integrated; no checkbox changed state (16/16 both before and after). Two notes:
+
+- **"Success criteria are measurable" was passed too generously in iteration 1.** SC-012 read
+ "measurably shorter" with no metric, which is exactly the unquantified-adjective case the item
+ exists to catch. It now states a structural claim that can be checked by reading the two
+ documents, so the item passes on its merits rather than by oversight.
+- **Two edge cases became requirements.** Removed-option precedence and the update-offer rule were
+ described in the spec as decisions still to be made; they are now FR-011 and FR-015, with matching
+ acceptance scenarios. Requirement IDs were renumbered to stay contiguous (former FR-011..FR-037
+ are now FR-012..FR-038); no downstream artifact referenced them yet.
diff --git a/specs/007-decommission-ai-analyzer/spec.md b/specs/007-decommission-ai-analyzer/spec.md
new file mode 100644
index 0000000..c613139
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/spec.md
@@ -0,0 +1,258 @@
+# Feature Specification: Decommission the FueliX AI Analyzer
+
+**Feature Branch**: `007-decommission-ai-analyzer`
+
+**Created**: 2026-07-26
+
+**Status**: Draft
+
+**Input**: User description: "BRD-004 — Decommission the FueliX AI Analyzer: the Cisco API check becomes the only mode (`brds/004-decommission-fuelix-ai-analyzer.md`)"
+
+## Clarifications
+
+### Session 2026-07-26
+
+- Q: When a removed option is combined with a management option (`--help`, `--version`, `--update`, `--uninstall`, `--config`), which wins? → A: Removed options are checked first and refuse regardless of what else is on the command line, so a stale option can never silently trigger an unintended action.
+- Q: Should a removed option exit with a distinct status so automation can tell it from a mistyped option? → A: No — reuse the existing argument-error status (`2`), since a removed option is an argument error; the message names the option for anything that needs to distinguish the two.
+- Q: What replaces "an advisory URL was supplied" as the test for skipping the start-of-run update offer? → A: The interactive-terminal check alone — an interactive run always gets the offer, a non-interactive run never does, and `--no-update-check` / the environment variable remain the explicit opt-outs.
+- Q: SC-012 said documentation would be "measurably shorter" without naming a metric. What should it measure? → A: A structural claim rather than a size one — each document presents exactly one way to run the tool, with no mode-comparison table, no mode-selection decision, and no second report format described.
+
+## User Scenarios & Testing *(mandatory)*
+
+### User Story 1 - Run the check with one command (Priority: P1)
+
+A security analyst puts their firewall inventory in a folder, types `caia`, and gets back the fleet-wide HTML impact report. There is no mode to choose, no advisory link to supply, and no AI service key to have obtained first. The behaviour previously reached only by adding `--api` is now simply what the tool does.
+
+**Why this priority**: This is the feature. Everything else in this spec supports, documents, or cleans up after it. Without it there is no change.
+
+**Independent Test**: Run `caia` with a valid inventory and configured Cisco credentials in a folder, and confirm a timestamped HTML report appears in `output/` with the same content the previous `--api` run produced for the same inputs. Delivers the whole value of the change on its own.
+
+**Acceptance Scenarios**:
+
+1. **Given** a valid inventory in the working folder and configured Cisco credentials, **When** the analyst runs `caia` with no other options, **Then** the inventory-driven check runs and a new timestamped HTML report is written to the output folder and its path is printed.
+2. **Given** the same inventory and credentials, **When** the run completes, **Then** every device's verdict, reason, advisory list, the report's content, the printed progress, and the process exit status are identical to what `caia --api` produced before this change.
+3. **Given** any run, **When** it completes, **Then** no spreadsheet report is produced, no advisory URL is requested or accepted, and no advisory CSAF file or temporary working folder is created.
+4. **Given** any run, **When** its outbound network activity is observed, **Then** it reaches only Cisco's advisory and token services and — unless suppressed — the version-check service, and no AI service.
+5. **Given** no Cisco credentials are configured, **When** the analyst runs `caia`, **Then** it refuses before making any request, names which credential is missing, and directs the user to `caia --config`.
+6. **Given** any option the product retains is supplied, **When** the tool runs, **Then** that option behaves exactly as it does today for the inventory-driven check, and its description in the help matches what it actually does.
+7. **Given** an interactive session on an out-of-date version, **When** the analyst runs `caia` with any combination of analysis options, **Then** the start-of-run update offer appears; **and given** the same in a non-interactive session, **Then** it does not.
+
+---
+
+### User Story 2 - Set up with one credential set (Priority: P1)
+
+A new user runs `caia --config` and is asked only for their Cisco client identifier and client secret. Nothing prompts for an AI key. A user upgrading from an earlier version, whose stored configuration still holds AI settings, sees those values quietly stop mattering rather than causing an error or a stale prompt.
+
+**Why this priority**: Configuration is the first thing every user touches, and the AI key is currently the first obstacle a new user hits. Halving the credential surface is a large part of the change's value and is testable on its own, independent of whether the run itself has been simplified yet.
+
+**Independent Test**: Run `caia --config` on a machine with a pre-existing configuration containing AI settings; confirm only Cisco values are prompted, the saved file afterwards carries only managed settings with owner-only permissions, and a run is unaffected by AI values left in the environment.
+
+**Acceptance Scenarios**:
+
+1. **Given** a fresh machine, **When** the user runs `caia --config`, **Then** they are prompted only for the Cisco client identifier and the Cisco client secret, and never for an AI key, model, or endpoint.
+2. **Given** a per-user configuration containing AI settings written by an earlier version, **When** any command runs, **Then** those values are ignored, nothing about the run changes, and no error or warning references them.
+3. **Given** that same configuration, **When** `caia --config` saves, **Then** the written file contains only the settings the product still manages, and retains its owner-only permissions and hand-editable format.
+4. **Given** an AI-related environment variable is set in the shell or present in a working-folder `.env`, **When** any command runs, **Then** it has no effect on behaviour and no message references it.
+5. **Given** Cisco rejects the credentials, or cannot be reached to verify them, **When** `caia --config` completes, **Then** it behaves exactly as it does today for each case — a rejection shows Cisco's reason and does not save, and an unreachable service is distinguished from a rejection and may be saved unverified.
+6. **Given** the per-user configuration file is opened by hand, **When** its header comments are read, **Then** they document only the settings and environment overrides the product still honours, under the product's current name.
+7. **Given** the user uninstalls the product, **When** it reports which saved settings were left behind, **Then** it names only credentials the product actually stores.
+8. **Given** a non-interactive session, **When** the product is uninstalled with the unattended-confirmation option, **Then** it completes without a prompt exactly as it does today.
+
+---
+
+### User Story 3 - A retired option says so (Priority: P2)
+
+A user or a script still passes an option that belonged to the retired mode. Instead of a bare "unrecognized argument", they get a refusal that names the option as removed and says what the tool does now.
+
+**Why this priority**: It protects everyone who already uses the tool from a confusing failure, but the product is correct without it — the options are gone either way. It is a quality-of-transition story, not a capability.
+
+**Independent Test**: Run the tool with each removed option in turn and confirm each produces a non-zero exit, a message naming that option as removed, and no analysis, no report, and no outbound request.
+
+**Acceptance Scenarios**:
+
+1. **Given** the former mode selector (`--api`) is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed and stating that the product now performs that check by default, and no analysis runs.
+2. **Given** the former advisory-URL option (`--url`) is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed and stating that the advisory-driven analysis no longer exists, and nothing is written and no advisory is fetched.
+3. **Given** either CSAF-retention option (`--keep-temp`, `--no-keep-temp`) is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed and stating that the product no longer downloads advisory files.
+4. **Given** the dry-run option (`--dry-run`) is supplied, **When** the tool runs, **Then** it exits non-zero with a message naming the option as removed, and no preview of the checks that would be made is produced.
+5. **Given** any removed option is supplied, **When** the tool refuses, **Then** the message is distinguishable from a generic unknown-argument error and tells the user what to run instead.
+6. **Given** a removed option is supplied alongside valid options, **When** the tool runs, **Then** it refuses on the removed option before loading the inventory or contacting anything.
+7. **Given** an unrecognised option that merely resembles a removed one is supplied, **When** the tool runs, **Then** it produces the ordinary unknown-argument error rather than the removal message.
+8. **Given** a removed option is supplied alongside any management option, **When** the tool runs, **Then** it refuses on the removed option and performs no management action — in particular `caia --uninstall --dry-run` refuses and does not uninstall.
+9. **Given** the help is displayed, **When** the options are listed, **Then** no removed option appears, and no listed option's description refers to the retired mode.
+
+---
+
+### User Story 4 - The tool describes itself accurately (Priority: P2)
+
+A prospective user reads the landing page or the README, or runs `caia --help`, and learns what the tool actually is: a deterministic checker that reports Cisco's own published verdicts, under a name that does not imply AI, with a prerequisites list containing no AI service.
+
+**Why this priority**: For a security tool, overstating how an answer is reached is a trust problem. It is separable from the code change and testable on its own, but the code change is what makes it true.
+
+**Independent Test**: Search every user-facing surface — product name, banner, help text, README, landing page, package metadata, sample environment file — for any AI claim or AI-provider reference, and confirm the prerequisites name only Cisco credentials.
+
+**Acceptance Scenarios**:
+
+1. **Given** any user-facing text, **When** it is read, **Then** it contains no claim of AI-driven or agentic analysis and no reference to an AI provider.
+2. **Given** the prerequisites in either document, **When** a new user follows them, **Then** the only credential they are told to obtain is Cisco PSIRT API access.
+3. **Given** the README and the landing page, **When** their install, configure, run, and update instructions are compared, **Then** they agree with each other and with the product's actual behaviour.
+4. **Given** the product name appears anywhere, **When** it is read, **Then** it is "Cisco Advisory Impact Analyzer", while the `caia` command, the `caia-cli` distribution, and the repository name are unchanged.
+5. **Given** the startup banner is shown on an interactive terminal, **When** it is read, **Then** it carries the current product name and the canonical tagline, and the banner still renders correctly with colour disabled and stays suppressed when not attached to a terminal.
+6. **Given** a returning user reads the documentation, **When** they look for the advisory-URL mode, **Then** they find a plain statement that it was removed and what replaces it.
+
+---
+
+### User Story 5 - Governance matches the product (Priority: P2)
+
+A maintainer planning the next feature finds that the project's constitution describes the tool that exists, so its Constitution Check is a meaningful gate rather than something to explain away.
+
+**Why this priority**: It unblocks all future planning and is a prerequisite of this feature's own plan, but it governs the work rather than being the work.
+
+**Independent Test**: Search the constitution for any AI-provider assumption and confirm none remains, that the conservative-analysis rules survive intact, and that the recorded network destinations match what the tool contacts.
+
+**Acceptance Scenarios**:
+
+1. **Given** the amended constitution, **When** it is searched, **Then** no principle, constraint, or quality gate references an AI provider, an AI-derived determination, the AI endpoint, or the spreadsheet report.
+2. **Given** the conservative-analysis principle, **When** it is read after amendment, **Then** it still forbids presenting an undecidable device as safe, still requires positive confirmation from Cisco's data before "not impacted", and uses a single undecided label.
+3. **Given** the quality gate requiring changes to be exercisable through a dry run, **When** the amended constitution is read, **Then** that gate has been deleted rather than reworded, because the capability it depended on is removed.
+4. **Given** any illustrative list of command-line options in the principles, **When** it is read, **Then** it cites only options that still exist.
+5. **Given** the recorded network destinations, **When** they are read, **Then** they list only the destinations the tool still contacts.
+6. **Given** the amendment, **When** it is committed, **Then** it carries an updated Sync Impact Report, a MAJOR version bump, and confirmation that the dependent templates remain aligned.
+
+---
+
+### User Story 6 - Nothing left behind (Priority: P3)
+
+A maintainer reviewing the change finds the removal is complete rather than partial: no dormant code that could be re-enabled by accident, no tests exercising a capability that no longer exists, no dependency retained for a reason that has gone.
+
+**Why this priority**: The product is already correct for users once the earlier stories land. This protects the codebase from carrying a retired mode as latent weight, which is a maintainer concern rather than a user-visible one.
+
+**Independent Test**: Search the codebase, samples, tests, and automated checks for the retired capability, and run the full test suite and the clean-room install test.
+
+**Acceptance Scenarios**:
+
+1. **Given** the completed change, **When** the codebase is searched for the AI provider's name, **Then** it appears nowhere outside the historical `specs/` and `brds/` records and the constitution's amendment history.
+2. **Given** the test suite, **When** it runs, **Then** it passes with no test referencing the retired mode.
+3. **Given** the clean-room install test, **When** it runs end to end, **Then** every step it performs applies to the surviving tool.
+4. **Given** the declared runtime dependencies, **When** they are reviewed, **Then** each is still required by surviving behaviour and any that is not has been removed.
+5. **Given** the released version, **When** it is inspected, **Then** it is 4.0.0 and the committed version, the git tag, and the version the installed tool reports agree.
+
+---
+
+### Edge Cases
+
+- **A configuration holding only obsolete settings**: a per-user file that contains AI values and no Cisco credentials. The run must refuse for missing Cisco credentials without mentioning the obsolete values, and the uninstall message must not claim to have preserved settings the product no longer manages.
+- **Obsolete values in the environment or a working-folder `.env`**: AI variables set in the shell, or present in a `.env` that is still loaded for automation. They must be inert — no effect, no message, no error.
+- **A removed option combined with valid options**: refusal must happen before the inventory is loaded or any request is made, so a removed option can never partially run.
+- **A removed option combined with a management option** (`--version`, `--update`, `--uninstall`, `--config`, `--help`): the removed option wins and the command refuses. Notably `caia --uninstall --dry-run` must refuse rather than uninstall, since a script author who wrote `--dry-run` there plausibly meant "rehearse this".
+- **An option that resembles a removed one** (for example a misspelling): must produce the ordinary unknown-argument error, not the "this was removed" message, so the removal notice stays meaningful.
+- **A terminal narrower than the banner's tagline**: the banner must still render sensibly, as it does today.
+- **A configuration saved by an older version, then read but never re-saved**: obsolete lines remain in the file indefinitely and must stay harmless; they are dropped only when the user next saves.
+- **An inventory that is empty, malformed, or undiscoverable**: must fail exactly as it does today, before any request is made — this is now the only pre-request validation the user gets.
+- **A user skipping both Cisco prompts during configuration**: setup must still complete, and the next run must refuse with a message naming what is missing.
+- **A run interrupted with Ctrl+C**: must still exit 130 with no traceback.
+
+## Requirements *(mandatory)*
+
+### Functional Requirements
+
+#### Single surviving mode
+
+- **FR-001**: System MUST, when invoked with no mode option, perform the inventory-driven check against Cisco's published advisory data and write the HTML impact report — the behaviour previously reached only via `--api`.
+- **FR-002**: System MUST NOT offer any advisory-URL or Event-Response-driven analysis; the capability is removed rather than hidden or deprecated.
+- **FR-003**: System MUST NOT download, retain, or create temporary storage for advisory CSAF files.
+- **FR-004**: System MUST NOT contact any AI service, and MUST NOT require, prompt for, read, or honour an AI service credential from any source — per-user configuration, environment variable, or working-folder `.env`.
+- **FR-005**: System MUST NOT produce a spreadsheet report; the single output of a run is the timestamped, self-contained HTML impact report.
+- **FR-006**: System MUST preserve the inventory-driven check's verdicts, reasons, advisory detail, report content, progress output, and exit statuses exactly; only the way the check is invoked changes.
+- **FR-007**: System MUST refuse to run, before making any request, when Cisco credentials are absent, naming what is missing and directing the user to the configuration command.
+
+#### Command-line surface
+
+- **FR-008**: System MUST remove every option belonging to the retired mode, and none MUST appear in the help: `--api`, `--url`, `--keep-temp`, `--no-keep-temp`, and `--dry-run`.
+- **FR-009**: System MUST remove the dry-run capability outright rather than restating it for the surviving check: no option may preview the checks that would be made without performing them. Every invocation consequently requires credentials and contacts Cisco.
+- **FR-010**: System MUST, when a removed option is supplied, refuse before performing any work, exit with the same status it already uses for an argument error, name the option as removed, and state what the product does instead, in a form distinguishable from a generic unknown-argument error. No new exit status is introduced; the message is what lets automation tell a retired option from a mistyped one.
+- **FR-011**: The check for removed options MUST take precedence over every other option, including the management options (`--help`, `--version`, `--update`, `--uninstall`, `--config`). A command line carrying a removed option MUST refuse and do nothing else, so a stale option can never silently trigger an action the user did not intend.
+- **FR-012**: System MUST continue to produce the ordinary unknown-argument error for an unrecognised option that is not one of the removed options.
+- **FR-013**: System MUST keep the current behaviour of every retained option, and every retained option's description MUST state what that option actually does under the surviving check, with no reference to an AI call, an advisory URL, or downloaded advisory files.
+- **FR-014**: System MUST retain the unattended-confirmation option (`--yes`, `-y`) with its behaviour intact, so uninstalling without a prompt in a non-interactive session remains possible, and MUST narrow its description to the uninstall confirmation, which is the only prompt it governs.
+- **FR-015**: System MUST decide whether to make the start-of-run update offer on the interactive-terminal check alone, replacing the withdrawn inference that a run is flag-driven because an advisory URL was supplied. An interactive run MUST receive the offer regardless of which analysis options were supplied; a non-interactive run MUST NOT. The existing explicit opt-outs — the update-check suppression option and its environment variable — MUST continue to work unchanged.
+
+#### Configuration
+
+- **FR-016**: The configuration command MUST prompt only for the Cisco client identifier and the Cisco client secret, and MUST NOT prompt for an AI key, model, or endpoint.
+- **FR-017**: The configuration command MUST preserve its existing behaviour for Cisco credentials: a visible identifier prompt, a non-echoing secret prompt with capture confirmation, skippable prompts, retention of an existing value on empty input, verification with Cisco before saving, and the distinction between a rejection and an unreachable service.
+- **FR-018**: When configuration is saved, the resulting per-user file MUST contain only settings the product still manages, MUST retain owner-only protection and its hand-editable format, and MUST carry header comments documenting only the settings and environment overrides still honoured, under the product's current name.
+- **FR-019**: System MUST NOT treat obsolete settings found in an existing configuration file or in the environment as an error, and MUST NOT reference them in any message.
+- **FR-020**: The message shown after uninstalling MUST name only credentials the product actually stores.
+
+#### Naming and description
+
+- **FR-021**: System MUST be presented as "Cisco Advisory Impact Analyzer", while the `caia` command, the `caia-cli` distribution name, and the repository name remain unchanged.
+- **FR-022**: Every user-facing description — product name, startup banner, command help, README, landing page, package metadata, sample environment file, and in-product text — MUST describe the product as producing deterministic verdicts from Cisco's published advisory data, and MUST contain no claim of AI-driven or agentic analysis and no reference to an AI provider.
+- **FR-023**: System MUST use one canonical tagline and one canonical one-line description, each verbatim wherever it appears. The tagline is **Fleet-wide firewall impact analysis, straight from Cisco**, used by the startup banner, the package metadata, the README tagline, and the landing page, carrying the ` · TELUS Digital` attribution on the surfaces that already include it. The one-line description is **Checks every firewall in your inventory against Cisco's published advisory data, and writes an HTML impact report.**
+- **FR-024**: Neither the tagline nor the one-line description may characterise the result as a binary affected/unaffected answer, since the product returns three verdicts.
+- **FR-025**: Documentation MUST list Cisco PSIRT API credentials as the only credential a user obtains, and MUST NOT list an AI service key among the prerequisites, the configuration steps, or the troubleshooting guidance.
+- **FR-026**: The README and the landing page MUST remain consistent with each other and with the product — one mode, one report, one credential set, and the current network destinations — within the same change.
+- **FR-027**: Documentation MUST state plainly that the advisory-URL mode has been removed and what a returning user should run instead.
+
+#### Governance
+
+- **FR-028**: The project constitution MUST be amended so that no principle, constraint, or quality gate references an AI provider, an AI-derived determination, the AI endpoint, or the spreadsheet report; the dry-run quality gate MUST be deleted rather than reworded; and any illustrative list of command-line options MUST cite only options that still exist.
+- **FR-029**: The amended constitution MUST retain the conservative-analysis principle in substance: an undecidable device MUST never be presented as safe, "not impacted" MUST rest on positive confirmation from Cisco's published data, every determination MUST be traceable to that data, and a single unambiguous undecided label MUST be used.
+- **FR-030**: The amended constitution MUST record only the network destinations the product still contacts, and MUST retain the rule that only the derived product family, hardware platform, and software release may leave the machine — never a firewall name or other inventory identifier.
+- **FR-031**: The constitutional amendment MUST carry an updated Sync Impact Report, a MAJOR version bump, and confirmation that the dependent templates remain aligned.
+
+#### Completeness and release
+
+- **FR-032**: No component, sample file, automated test, or clean-room install check may retain a dependency on the removed capability, and the surviving test suite MUST pass.
+- **FR-033**: A completed run MUST demonstrably contact only Cisco's advisory and token services and — unless suppressed — the version-check service, with no request to any AI endpoint.
+- **FR-034**: The clean-room install test MUST exercise only surviving flows, including a run invoked with no mode option and a refusal when credentials are absent.
+- **FR-035**: The sample environment file MUST document only the variables the product still honours.
+- **FR-036**: Each declared runtime dependency MUST be confirmed still required by surviving behaviour, and any dependency retained solely for the removed capability MUST be removed.
+- **FR-037**: The product version MUST be raised to 4.0.0, reflecting the removal of documented options and a documented mode.
+- **FR-038**: Historical specification and BRD records MUST be left as written; correcting the product's description MUST NOT involve rewriting the record of past decisions.
+
+### Key Entities
+
+- **Per-user configuration record**: the stored settings the product manages, kept in an OS-appropriate per-user location outside any working folder. Its managed set shrinks to the Cisco client identifier and client secret; values outside that set are neither read nor written, and are dropped from the file the next time it is saved.
+- **Retired option**: a command-line option the product once accepted and no longer does. Each carries a refusal message naming it and pointing at current behaviour, distinguishing it from an option the product never had.
+- **Canonical product copy**: the single tagline and single one-line description, each with exactly one authoritative wording reused across the banner, package metadata, README, and landing page, so the surfaces cannot drift apart.
+
+## Success Criteria *(mandatory)*
+
+### Measurable Outcomes
+
+- **SC-001**: A user with Cisco credentials configured obtains a complete impact report by typing one command with no options.
+- **SC-002**: A new user reaches their first successful run having obtained exactly one credential set.
+- **SC-003**: 100% of runs produce verdicts, reasons, and report content identical to what the previous `--api` mode produced for the same inventory and credentials.
+- **SC-004**: 0 requests to any AI endpoint across any run, in any form of invocation, including with obsolete environment variables set.
+- **SC-005**: 0 occurrences of the AI provider's name in the product, its documentation, its samples, its tests, and its governing constitution, outside historical records and the constitution's amendment history.
+- **SC-006**: 100% of invocations using a removed option are refused before any work is done, with a message that names the option and states what to run instead.
+- **SC-007**: A user whose stored configuration or environment still carries obsolete settings sees no change in behaviour and no error attributable to them.
+- **SC-008**: 0 references to an AI service key remain in the prerequisites, configuration steps, or troubleshooting guidance of either the README or the landing page, and their instructions agree with each other and with the product.
+- **SC-009**: The constitution's Constitution Check can be answered affirmatively for the product as shipped, with no gate requiring an exception or explanation.
+- **SC-010**: The test suite passes with no test exercising the removed capability, and the clean-room install test completes end to end.
+- **SC-011**: The released version is 4.0.0, and the committed version, the git tag, and the version the installed tool reports all agree.
+- **SC-012**: The README and the landing page each present exactly one way to run the tool: no mode-comparison table, no mode-selection decision left to the reader, and no second report format described.
+
+## Assumptions
+
+Decisions taken as reasonable defaults where BRD-004 left a question open. Each can be revisited in `/speckit-clarify`.
+
+- **Obsolete settings**: values the product no longer manages are dropped only when the user next saves configuration. Nothing is pruned during an ordinary run, because silently rewriting the user's file outside an explicit save would be surprising.
+- **Removal notice placement**: the statement that the advisory-URL mode was removed appears in both the README and the release notes, so both a returning reader and an upgrading user meet it.
+- **Working-folder `.env` support**: retained for automation, with only its AI variables removed. Removing `.env` support entirely is out of scope for this change (BRD-004 §5.2).
+- **Report heading**: the report's own heading is already "Cisco Advisory Impact Report" — it carries no AI claim and no "Agent" — so it stays as written and does not track the product's new name.
+- **Existing users**: users of the retired mode are known to the product owner and are told directly. The product's only obligation to them is a clear refusal message and accurate documentation; no migration tooling is provided.
+- **Spreadsheet consumers**: nothing downstream depends on the Excel report as a machine-readable artifact.
+- **Inventory reading**: reading the inventory spreadsheet remains necessary, so the spreadsheet-reading dependency stays even though spreadsheet writing is removed.
+- **Version increment**: removing a documented mode and five documented options is breaking under semantic versioning, making 4.0.0 correct for the product and a MAJOR bump correct for the constitution.
+- **Repository description**: the GitHub repository description and any external listing of the product are updated by the product owner outside this change.
+
+### Dependencies
+
+- **Constitution amendment (input, ordering)**: the amendment must be in place before this feature's Constitution Check can be answered honestly. This is the only ordering dependency in the change.
+- **BRD-003 / spec 006 (input)**: define the behaviour being promoted to the product's only behaviour. This feature inherits those requirements rather than restating them.
+- **Cisco PSIRT advisory and authentication services (input)**: now the product's only source of verdicts and its only analysis-time external dependency.
+- **Self-update and self-uninstall capabilities (related)**: untouched by this change but sharing the command surface, so they must keep working after the option set changes.
+- **Clean-room install test (output)**: currently exercises the retired mode and must be reduced to surviving flows.
+- **README and landing page (output)**: must be corrected within the same change to satisfy the Documentation quality gate.
From bf7f722c28568ca4e0ab2a3b30bea49487cbe2e9 Mon Sep 17 00:00:00 2001
From: Ali Bahaloo
Date: Sun, 26 Jul 2026 23:16:04 -0700
Subject: [PATCH 2/4] docs(007): implementation plan, research, data model, and
contracts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 0 and Phase 1 design output for the FueliX decommission.
- plan.md: 12/12 constitution gates pass (v3.0.0), no violations; file-by-file
DELETE/EDIT/KEEP map across caia/, tests/, docs, and packaging.
- research.md: 11 decisions. D1 is load-bearing — argparse gives --help
precedence over a merely-absent option (verified: `caia --api --help` exits 0
and prints help), so the removed-option guard must scan argv before the parser
is built. Without it `caia --uninstall --dry-run` would really uninstall,
which is the case FR-011 exists to prevent.
- data-model.md: the 3 entities that change shape and the 7 that must not,
since behavioural equivalence is what a deletion through shared code threatens.
- contracts/: post-change CLI surface (options, refusal messages, precedence,
exit codes), per-user config format, and the canonical product copy with the
13 surfaces each string appears on.
- quickstart.md: 7 validation scenarios mapped to the user stories. Scenario 0
captures the equivalence baseline and must run before any deletion.
No product code changes yet.
Co-Authored-By: Claude Opus 5
---
.../contracts/cli-surface.md | 130 +++++++++
.../contracts/config-file.md | 98 +++++++
.../contracts/product-copy.md | 94 +++++++
.../data-model.md | 119 +++++++++
specs/007-decommission-ai-analyzer/plan.md | 206 +++++++++++++++
.../quickstart.md | 243 +++++++++++++++++
.../007-decommission-ai-analyzer/research.md | 249 ++++++++++++++++++
7 files changed, 1139 insertions(+)
create mode 100644 specs/007-decommission-ai-analyzer/contracts/cli-surface.md
create mode 100644 specs/007-decommission-ai-analyzer/contracts/config-file.md
create mode 100644 specs/007-decommission-ai-analyzer/contracts/product-copy.md
create mode 100644 specs/007-decommission-ai-analyzer/data-model.md
create mode 100644 specs/007-decommission-ai-analyzer/plan.md
create mode 100644 specs/007-decommission-ai-analyzer/quickstart.md
create mode 100644 specs/007-decommission-ai-analyzer/research.md
diff --git a/specs/007-decommission-ai-analyzer/contracts/cli-surface.md b/specs/007-decommission-ai-analyzer/contracts/cli-surface.md
new file mode 100644
index 0000000..0712de1
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/contracts/cli-surface.md
@@ -0,0 +1,130 @@
+# Contract: Command-Line Surface
+
+**Feature**: [../spec.md](../spec.md) | **Date**: 2026-07-26
+
+The complete `caia` command surface after this feature. This is the tool's public contract with its
+users and their scripts; anything not listed here is not offered.
+
+---
+
+## 1. Invocation
+
+```text
+caia [--inventory PATH] [--sheet NAME] [--output-dir DIR] [--no-update-check]
+caia --config
+caia --version | --update | --uninstall [--yes]
+caia --help
+```
+
+Invoked with no options, `caia` performs the inventory-driven check: it discovers the inventory in
+the current folder, asks Cisco which advisories apply to each distinct family/platform/release, and
+writes one timestamped HTML report into `./output`.
+
+## 2. Surviving options
+
+| Option | Behaviour | Description shown in help |
+|---|---|---|
+| `-h`, `--help` | Show options and exit 0 | *(argparse default)* |
+| `--version` | Print installed version; best-effort newer-version notice | Print the installed version and check for a newer one |
+| `--update` | Reinstall the latest published version via uv | Update to the latest published version via uv |
+| `--uninstall` | Remove the uv-installed tool; per-user config is preserved | Remove caia via uv (your saved config is kept) |
+| `--config` | Interactive setup for Cisco credentials | **Set your Cisco PSIRT API credentials (stored per-user)** |
+| `--yes`, `-y` | Skip the uninstall confirmation | **Skip the uninstall confirmation (for scripted/unattended use)** |
+| `--inventory PATH` | Use this inventory file instead of discovering one | Path to an inventory .xlsx (else discovered in the current folder) |
+| `--sheet NAME` | Inventory sheet name (default `FW_List`) | Inventory sheet name |
+| `--output-dir DIR` | Where to write the report (default `./output`) | Where to write the report (default: ./output in the current folder) |
+| `--no-update-check` | Skip the start-of-run check for a newer version | Skip the start-of-run check for a newer version |
+
+**Bolded descriptions change** in this feature (FR-013, FR-014). The rest are unchanged and must
+stay unchanged.
+
+**Parser description** (shown under the banner in `--help`):
+
+> Checks every firewall in your inventory against Cisco's published advisory data, and writes an
+> HTML impact report.
+
+## 3. Removed options
+
+Each of these was previously accepted and is now refused. The guard matches the option name
+**exactly** (research D2); `--url=` is also matched, and the value is never inspected.
+
+| Option | Refusal message |
+|---|---|
+| `--api` | `--api has been removed. Checking your whole inventory against Cisco's published advisory data is now what caia does by default — just run: caia` |
+| `--url` | `--url has been removed. The advisory-driven analysis no longer exists; caia now checks your whole inventory against Cisco's published advisory data. Just run: caia` |
+| `--keep-temp` | `--keep-temp has been removed. caia no longer downloads advisory files, so there is nothing to keep.` |
+| `--no-keep-temp` | `--no-keep-temp has been removed. caia no longer downloads advisory files, so there is nothing to delete.` |
+| `--dry-run` | `--dry-run has been removed. caia no longer offers a preview; every run contacts Cisco and needs your credentials.` |
+
+Message requirements (FR-010): each names the option, states what the product does instead, and is
+distinguishable from argparse's generic `unrecognized arguments` text.
+
+## 4. Precedence
+
+**The removed-option guard runs first — before the parser is built, before the banner logic, before
+any file is read or any request is made.**
+
+```text
+argv
+ │
+ ├─ contains a removed option? ──yes──→ print refusal, exit 2, do nothing else
+ │
+ no
+ │
+ ▼
+banner → parse args → dispatch (--version / --update / --uninstall / --config / run)
+```
+
+Consequences, all required by FR-011:
+
+| Command line | Result |
+|---|---|
+| `caia --api --help` | refusal naming `--api`, exit 2 — **not** help |
+| `caia --help --api` | refusal naming `--api`, exit 2 |
+| `caia --uninstall --dry-run` | refusal naming `--dry-run`, exit 2 — **does not uninstall** |
+| `caia --version --url X` | refusal naming `--url`, exit 2 |
+| `caia --api --inventory fw.xlsx` | refusal naming `--api`, exit 2, inventory never loaded |
+| `caia --dry` | argparse's generic unrecognised-argument error, exit 2 — **not** a removal message |
+| `caia --urls X` | argparse's generic unrecognised-argument error, exit 2 |
+
+When more than one removed option is present, refusing on the first one encountered is sufficient;
+the contract does not require listing them all.
+
+## 5. Exit codes
+
+Unchanged from the current tool except that removed options join the argument-error case.
+
+| Code | Meaning |
+|---|---|
+| `0` | Success (including `--help`, and an update/uninstall that had nothing to do) |
+| `1` | User declined a confirmation |
+| `2` | Argument error — unrecognised option, invalid combination, **or a removed option** |
+| `3` | Version lookup failed (GitHub unreachable or rate-limited) |
+| `4` | Update or uninstall failed |
+| `130` | Interrupted (Ctrl+C), no traceback |
+
+No new exit code is introduced (research D3).
+
+## 6. Credential requirement
+
+Every invocation that performs the check requires Cisco credentials; there is no offline path.
+
+- Missing credentials → refuse **before any request**, naming which value is absent and directing
+ the user to `caia --config` (FR-007). Exit non-zero.
+- The refusal must not mention any AI credential, and must not be affected by AI values present in
+ the environment or in the configuration file (FR-004, FR-019).
+
+## 7. Environment variables
+
+| Variable | Effect |
+|---|---|
+| `CISCO_CLIENT_ID`, `CISCO_CLIENT_SECRET` | Override the stored credentials |
+| `CAIA_NO_UPDATE_CHECK` | Skip the start-of-run update check |
+| `CAIA_NO_BANNER` | Suppress the splash banner |
+| `FUELIX_*` | **No effect.** Not read, not reported, not an error |
+
+## 8. Start-of-run update offer
+
+Appears when *all* of: the installed version is out of date, stdin is a TTY, and neither
+`--no-update-check` nor `CAIA_NO_UPDATE_CHECK` is set. Which analysis options were supplied is
+irrelevant (FR-015, research D4). Answering yes updates and exits; answering no continues.
diff --git a/specs/007-decommission-ai-analyzer/contracts/config-file.md b/specs/007-decommission-ai-analyzer/contracts/config-file.md
new file mode 100644
index 0000000..660e86e
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/contracts/config-file.md
@@ -0,0 +1,98 @@
+# Contract: Per-User Configuration File
+
+**Feature**: [../spec.md](../spec.md) | **Date**: 2026-07-26
+
+Supersedes the configuration contract in
+[specs/004-uv-tool-distribution/contracts/config.md](../../004-uv-tool-distribution/contracts/config.md)
+for the managed key set only. Location, format, permissions, and resolution order are unchanged.
+
+---
+
+## 1. Location and permissions — unchanged
+
+| Platform | Path |
+|---|---|
+| Windows | `%APPDATA%\caia\config` |
+| macOS | `~/Library/Application Support/caia/config` |
+| Linux | `$XDG_CONFIG_HOME/caia/config`, else `~/.config/caia/config` |
+
+Owner-only (`0600`) where the OS supports it; written atomically via a temp file and replace. Always
+outside any working folder, so a run from any folder finds it.
+
+## 2. Managed keys
+
+| Key | Managed | Secret | Prompted |
+|---|---|---|---|
+| `CISCO_CLIENT_ID` | yes | no — echoed so a paste can be confirmed | visibly |
+| `CISCO_CLIENT_SECRET` | yes | yes — never echoed or logged | hidden, with capture confirmation |
+
+**No other key is managed.** `FUELIX_API_KEY`, `FUELIX_MODEL`, and `FUELIX_BASE_URL` were managed
+before this feature and are not now.
+
+## 3. Resolution order — unchanged
+
+```text
+environment variable → config file → default
+```
+
+The defaults map is now empty, so an absent setting resolves to the empty string. A working-folder
+`.env` is still loaded for automation, applied with `setdefault` so real environment variables win;
+only its AI variables are withdrawn.
+
+## 4. File format
+
+```text
+# Cisco Advisory Impact Analyzer configuration.
+# Managed by `caia --config`; you may edit values by hand.
+# Environment variables of the same name override every value below:
+# CISCO_CLIENT_ID / CISCO_CLIENT_SECRET - Cisco PSIRT API access
+CISCO_CLIENT_ID=
+CISCO_CLIENT_SECRET=
+```
+
+The comment header is regenerated on every save and must name the current product and only the
+surviving overrides (FR-018). Empty values are omitted rather than written blank.
+
+## 5. Behaviour with a file written by an earlier version
+
+Given a file that still contains AI settings:
+
+| Operation | Behaviour |
+|---|---|
+| Any run reads the file | Managed keys used; unmanaged keys parsed but never requested, so inert |
+| Any run encounters unmanaged keys | **No error, no warning, no mention** (FR-019) |
+| `caia --config` saves | File is rewritten from the managed key set: obsolete lines disappear, header regenerated, permissions reapplied |
+| User never runs `--config` | Obsolete lines remain on disk indefinitely and stay harmless |
+
+There is no migration step, no pruning pass, and no "first run after upgrade" detection. The drop is
+a side effect of the save path building its output from the managed key set (research D5).
+
+## 6. Interactive `caia --config`
+
+One linear pass, every prompt skippable:
+
+```text
+Configure Cisco Advisory Impact Analyzer
+ Cisco PSIRT API — https://developer.cisco.com/psirt/
+ Cisco client ID (visible prompt; existing value shown; Enter keeps it)
+ Cisco client secret (hidden prompt; capture length confirmed; Enter keeps it)
+ → verify with Cisco when both values are available for the session
+ → save, report the path
+```
+
+**Preserved exactly** (FR-017): visible identifier prompt, non-echoing secret prompt with capture
+confirmation, skippable prompts, empty input keeps an existing value, verification before saving,
+and the three-way outcome — accepted / rejected (show Cisco's reason, do not save) / unreachable
+(distinguish from rejection, offer to save unverified). No message may contain any part of a
+credential.
+
+**Removed**: the FueliX section — API key prompt, model selection, and the base-URL note in the
+completion output.
+
+**Changed**: the screen title now names the current product; the trailing hints mention only Cisco.
+
+## 7. Uninstall disclosure
+
+After a successful uninstall the tool reports where the configuration was left. The wording must
+name only credentials the product actually stores (FR-020) — the current text says "API key and
+model", which describes the retired mode.
diff --git a/specs/007-decommission-ai-analyzer/contracts/product-copy.md b/specs/007-decommission-ai-analyzer/contracts/product-copy.md
new file mode 100644
index 0000000..f138836
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/contracts/product-copy.md
@@ -0,0 +1,94 @@
+# Contract: Canonical Product Copy
+
+**Feature**: [../spec.md](../spec.md) | **Date**: 2026-07-26
+
+FR-023 requires the tagline and one-line description to be used **verbatim** wherever they appear.
+No language shares a string across Python, TOML, Markdown, and HTML, so this file is the single
+source the copies are taken from and checked against.
+
+---
+
+## 1. The strings
+
+### Product name
+
+```text
+Cisco Advisory Impact Analyzer
+```
+
+Replaces "Cisco Advisory Impact Agent". The `caia` command, the `caia-cli` distribution name, and
+the `xavient/caia-cli` repository name are **unchanged** (FR-021).
+
+### Tagline
+
+```text
+Fleet-wide firewall impact analysis, straight from Cisco
+```
+
+72 characters including the attribution suffix below — one shorter than the current tagline, so no
+banner layout risk at 80 columns.
+
+### Attribution suffix (banner only)
+
+```text
+ · TELUS Digital
+```
+
+Appended to the tagline **only** on the startup banner, which is how the current banner reads. The
+package description, README tagline, and landing-page tagline carry the tagline alone. This
+asymmetry is deliberate — do not "fix" it.
+
+### One-line command description
+
+```text
+Checks every firewall in your inventory against Cisco's published advisory data, and writes an HTML impact report.
+```
+
+## 2. Where each appears
+
+| Surface | Location | String |
+|---|---|---|
+| Startup banner | `caia/ui.py` → `_NAME` | Product name |
+| Startup banner | `caia/ui.py` → `_SLOGAN` | Tagline + attribution suffix |
+| Command help | `caia/cli.py` → parser `description` | One-line description |
+| Package metadata | `pyproject.toml` → `[project] description` | Tagline |
+| Package docstring | `caia/__init__.py` | Product name + tagline (prose) |
+| README heading | `README.md` → `
` | Product name |
+| Landing page tagline | `docs/index.html` → `.tagline` | Tagline (or a hero line consistent with it) |
+| Configuration screen | `caia/config.py` → `run_config` title | Product name |
+| Configuration file header | `caia/config.py` → `save` | Product name |
+| Constitution | `.specify/memory/constitution.md` | Product name — **already done** (v3.0.0) |
+
+## 3. Prohibited content
+
+No surface in the table above may contain (FR-022):
+
+- Any claim of AI-driven, AI-assisted, or agentic analysis
+- The word "Agent" as part of the product name
+- Any reference to an AI provider or model
+- A characterisation of the result as a binary affected/unaffected answer (FR-024) — the product
+ returns three verdicts, and Needs review is a third of the point
+
+## 4. Unchanged by this contract
+
+The impact report's own heading and document title remain:
+
+```text
+Cisco Advisory Impact Report
+```
+
+It carries no AI claim and no "Agent", so it does not track the product's new name (spec
+Assumptions). Leave `caia/html_report.py` untouched.
+
+## 5. Verification
+
+The tagline and description each appear the same number of times as rows in the table above, with
+identical wording. A search for the old tagline, the old product name, or any AI-provider name
+across the product, its documentation, its samples, and its tests returns nothing outside historical
+`specs/` and `brds/` records and the constitution's amendment history (SC-005).
+
+Note that `tests/test_banner.py` asserts the product name literally, so the rename has a failing
+test until the banner is updated — intentional.
diff --git a/specs/007-decommission-ai-analyzer/data-model.md b/specs/007-decommission-ai-analyzer/data-model.md
new file mode 100644
index 0000000..d5ef3e0
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/data-model.md
@@ -0,0 +1,119 @@
+# Data Model: Decommission the FueliX AI Analyzer
+
+**Feature**: [spec.md](./spec.md) | **Plan**: [plan.md](./plan.md) | **Date**: 2026-07-26
+
+This feature removes far more data structures than it changes. The section that matters most is the
+second one — the entities that must **not** change — because behavioural equivalence (FR-006) is
+the constraint most at risk during a deletion that runs through shared code.
+
+---
+
+## 1. Entities that change
+
+### Per-user configuration record
+
+The stored settings the product manages, in an OS-appropriate per-user location outside any working
+folder. Format is unchanged (`KEY=value` lines, `#` comments, owner-only permissions); only the
+managed key set shrinks. Full format in [contracts/config-file.md](./contracts/config-file.md).
+
+| Field | Before | After | Notes |
+|---|---|---|---|
+| `CISCO_CLIENT_ID` | managed | **managed** | not a secret; prompted visibly |
+| `CISCO_CLIENT_SECRET` | managed | **managed** | secret; never echoed or logged |
+| `FUELIX_API_KEY` | managed | *unmanaged* | ignored on read; dropped on next save |
+| `FUELIX_MODEL` | managed | *unmanaged* | ignored on read; dropped on next save |
+| `FUELIX_BASE_URL` | managed | *unmanaged* | ignored on read; dropped on next save |
+
+**Validation rules**
+
+- A value is resolved as: environment variable → config file → default. Unchanged.
+- The defaults map becomes empty; an absent setting resolves to the empty string.
+- Both Cisco values are optional at configuration time (prompts stay skippable) and required at run
+ time (FR-007 refuses, naming what is missing).
+- Unmanaged keys present in the file are neither an error nor a warning (FR-019).
+
+**Lifecycle**
+
+```text
+written by an older version ─┐
+ ├─→ read: managed keys used, unmanaged keys inert
+ └─→ saved by --config: file rewritten from the managed
+ key set only; unmanaged keys disappear, permissions
+ and comment header regenerated
+```
+
+There is no migration step and no pruning pass. The drop is a side effect of `save()` building its
+output from the managed key set, not a dedicated operation.
+
+### Removed-option registry
+
+New. A fixed mapping from a withdrawn option name to the explanation shown when someone uses it.
+Consulted by the guard before argument parsing (research D1); it is a constant, not persisted state.
+
+| Attribute | Description |
+|---|---|
+| Option name | Exact spelling, e.g. `--dry-run`. Matched exactly; abbreviations are not members (research D2) |
+| Replacement guidance | What the product does instead, stated in the refusal |
+
+Members: `--api`, `--url`, `--keep-temp`, `--no-keep-temp`, `--dry-run`. Messages are specified in
+[contracts/cli-surface.md](./contracts/cli-surface.md).
+
+### Canonical product copy
+
+Not runtime data, but it has exactly one authoritative value per string and multiple copies that
+must agree, which is what makes it worth modelling. Defined in
+[contracts/product-copy.md](./contracts/product-copy.md).
+
+| String | Authoritative value | Appears on |
+|---|---|---|
+| Product name | Cisco Advisory Impact Analyzer | banner, README, landing page, constitution, config header |
+| Tagline | Fleet-wide firewall impact analysis, straight from Cisco | banner (with attribution suffix), package metadata, README, landing page |
+| One-line description | Checks every firewall in your inventory against Cisco's published advisory data, and writes an HTML impact report. | command help |
+
+---
+
+## 2. Entities that must NOT change
+
+These carry the surviving check's behaviour. FR-006 forbids any observable change to them, and
+SC-003 measures it. They are listed here so a reviewer can check the diff against a stated
+expectation rather than an impression.
+
+| Entity | Owner | Why it is at risk |
+|---|---|---|
+| **Device record** (name, model, family, version, priority, raw column map) | `inventory.load_inventory` | The same module loses `build_combos`/`expand_combo_ids`; the loader sits next to them |
+| **Check tuple** (os_type, alias, release) | `matching.build_checks` | The module has a parameter removed (research D6); anything beyond that is out of scope |
+| **Verdict** (Impacted / Not impacted / Needs review) | `matching.verdict_from_outcome` | Principle IV governs it; untouched by this feature |
+| **Reason** (kind + human-readable detail) | `matching` | Reason text appears in the report; a reworded reason is an observable change |
+| **Advisory record** (id, title, severity, CVEs, first fixed, URL) | `psirt.Client` | Untouched; only the module docstring changes |
+| **Run summary** (generated-at, inventory file, device count, per-verdict counts, partial cause) | `analyzer._build_summary` | Lives in the file being halved |
+| **Impact report** (structure, styling, filter/sort behaviour, coverage disclosure) | `html_report` | Untouched; its heading stays "Cisco Advisory Impact Report" (spec Assumptions) |
+
+**Deleted entirely** (no consumer survives): the advisory extraction record (id, title, vulnerable
+products, not-vulnerable products, affected versions) from `cisco.py`; the inventory combo (id,
+model, family, version, count) and its id→names map from `inventory.py`; the AI assessment result
+(category, impacted combo ids, summary, reasoning) from `fuelix.py`; the spreadsheet report row
+(advisory id, description, assessment) from `report.py`.
+
+---
+
+## 3. State transitions
+
+Only one flow in this feature has states worth drawing: what a user meets after upgrading.
+
+```text
+ ┌──────────────────────────────────────────┐
+ upgrade to 4.0.0 │ config holds Cisco creds │──→ `caia` runs normally
+ ─────────→ │ config holds only obsolete AI settings │──→ refuses, names the missing
+ │ config absent │ Cisco credential, points at
+ │ │ `caia --config`
+ └──────────────────────────────────────────┘
+ │
+ runs an old command line (`--api`, `--url`, …)
+ │
+ ▼
+ refusal naming the option, exit 2, nothing else happens
+```
+
+No state is persisted about the upgrade itself: the product does not record that it once had
+another mode, and does not detect "first run after upgrade". The refusal messages are the entire
+migration mechanism.
diff --git a/specs/007-decommission-ai-analyzer/plan.md b/specs/007-decommission-ai-analyzer/plan.md
new file mode 100644
index 0000000..0259149
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/plan.md
@@ -0,0 +1,206 @@
+# Implementation Plan: Decommission the FueliX AI Analyzer
+
+**Branch**: `007-decommission-ai-analyzer` | **Date**: 2026-07-26 | **Spec**: [spec.md](./spec.md)
+
+**Input**: Feature specification from `/specs/007-decommission-ai-analyzer/spec.md`
+
+## Summary
+
+Retire the advisory-driven, AI-assisted analysis so the inventory-driven Cisco PSIRT check
+delivered by spec 006 becomes the tool's only behaviour. Running `caia` with no options now does
+what `caia --api` used to do; the FueliX client, the ERP/CSAF fetch-and-extract path, the Excel
+report writer, the AI credential, and five command-line options are deleted outright.
+
+This is a subtraction, and the design's centre of gravity is **not changing the surviving check
+while removing the mode it shares plumbing with**. `analyzer.py` holds both flows and they share
+inventory discovery, output-directory resolution, and error helpers; `cli.py` holds one parser for
+both; `config.py` holds both credential sets. The risk is not that the deletion is hard — it is
+that a shared helper gets altered in passing and a verdict changes. FR-006 makes behavioural
+equivalence a requirement and SC-003 measures it, so the existing `tests/test_api_flow.py` suite is
+treated as a frozen regression guard: it must pass with no substantive edit beyond dropping the
+four dry-run cases and the AI-key case.
+
+Three modules are deleted (`fuelix.py`, `cisco.py`, `report.py` — 476 lines), `analyzer.py` loses
+roughly its first half, and `inventory.py` loses the AI-only combo compaction. One genuinely new
+piece of behaviour is added: a **removed-option guard**. FR-011 requires a retired option to
+outrank every other option including `--help`, and argparse cannot express that — verified
+empirically, `caia --api --help` prints help and exits 0 once `--api` is simply absent from the
+parser. The guard is therefore an explicit scan of `argv` before the parser is built.
+
+No new runtime dependency; two are re-justified and both stay. The constitution was amended to
+3.0.0 as a prerequisite (already committed on this branch), removing every AI-provider rule and
+deleting the dry-run quality gate.
+
+## Technical Context
+
+**Language/Version**: Python 3.9+ (matches `requires-python` in `pyproject.toml`)
+
+**Primary Dependencies**: None added; none removed. `openpyxl` is re-justified — it is still
+required to *read* the inventory workbook (`inventory.load_inventory`) even though the Excel
+*writer* is deleted with `report.py`. `python-dotenv` is re-justified — working-folder `.env`
+support is retained for automation (spec Assumptions), with only its AI variables withdrawn.
+Standard library only for everything this feature touches: `argparse` (option surface), `sys`
+(argv scan, TTY checks), `pathlib`/`os` (config paths, unchanged).
+
+**Storage**: The per-user configuration file, whose managed key set shrinks from five keys to two.
+No format change, no migration step — obsolete keys are ignored on read and dropped on the next
+save. See [contracts/config-file.md](./contracts/config-file.md).
+
+**Testing**: `unittest` + `unittest.mock` under `tests/`. `tests/test_extraction.py` is deleted
+whole (it tests CSAF extraction only). `tests/test_api_flow.py` is the frozen regression guard for
+FR-006 — five cases removed, the remaining 33 must pass unchanged. Pruned: `tests/test_config.py`
+(AI settings and prompts), `tests/test_cli.py` (`ApiFlagTests` → removed-option guard tests),
+`tests/test_folders.py` (`WriteReportTests`), `tests/test_banner.py` (product name assertion).
+New coverage: the removed-option guard, including precedence over `--help` and the
+resembling-option case.
+
+**Target Platform**: macOS, Windows, Linux (Constitution II).
+
+**Project Type**: Single-project CLI (installed as the `caia` console script).
+
+**Performance Goals**: Unchanged. This feature adds no work to a run; the argv scan is O(number of
+arguments) and runs before anything else. The surviving check's timing profile is entirely spec
+006's and must not move.
+
+**Constraints**: Behavioural equivalence of the surviving check is a hard constraint, not a goal
+(FR-006, SC-003). Reports written by earlier versions must remain readable and untouched. Every
+invocation now requires Cisco credentials and contacts Cisco — there is no offline path left, which
+is the accepted cost of removing the dry run.
+
+**Scale/Scope**: ~800 lines deleted across 3 whole modules and 4 partial ones; ~40 lines of new
+guard logic; 5 command-line options withdrawn; 5 documentation and packaging surfaces corrected;
+1 constitutional amendment (already landed).
+
+## Constitution Check
+
+*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
+
+Evaluated against constitution **v3.0.0** (amended on this branch as a prerequisite of this
+feature; see [.specify/memory/constitution.md](../../.specify/memory/constitution.md)).
+
+| Principle / gate | Verdict | Evidence |
+|---|---|---|
+| **I. Standard-Library-First & Minimal Dependencies** | ✅ Pass | Net removal of code and of one external service. No dependency added. Both surviving dependencies are re-justified per the principle's new "a dependency that outlives its purpose MUST be removed" clause: `openpyxl` still reads the inventory, `python-dotenv` still loads `.env` (research D7). |
+| **II. Cross-Platform Parity** | ✅ Pass | No OS-conditional code added or removed. Config path resolution, the only OS-aware logic touched, is untouched in substance — only its managed key set changes. |
+| **III. CLI-First, Scriptable Interface** | ✅ Pass | Every surviving capability stays reachable from the command line, flag-driven operation is preserved, exit codes keep their meanings (FR-010 reuses the argument-error status; `130` on interrupt unchanged). The principle's clause that a withdrawn option MUST be refused by name rather than falling through to a generic error is satisfied by FR-010/FR-011 and is the reason the removed-option guard exists. |
+| **IV. Conservative & Traceable Analysis (NON-NEGOTIABLE)** | ✅ Pass | The verdict logic in `matching.py` is not touched except to drop a now-dead parameter (research D6). `Needs review` becomes the sole undecided label, as the amended principle requires. Positive confirmation before `Not impacted` is untouched. FR-006 forbids any observable change to verdicts, and SC-003 measures it. |
+| **V. Secrets Hygiene & Data Locality** | ✅ Pass | Strictly improved: one credential instead of two, one fewer external destination, and the AI endpoint — which received the largest disclosure — is gone. The principle's bound (only family, platform, release may leave the machine) is now the tool's actual maximum rather than its minimum. |
+| **Source of truth** (Cisco PSIRT API only) | ✅ Pass | ERP pages and CSAF JSON are no longer fetched; `sec.cloudapps.cisco.com` survives only as link text inside the report. |
+| **Inputs/outputs** (one timestamped HTML report) | ✅ Pass | The Excel writer is deleted; `impact_report_*.html` becomes the only output. Never-overwrite and self-contained rendering are untouched. |
+| **Networking** (declared destinations only) | ✅ Pass | Contacted destinations reduce to `apix.cisco.com`, `id.cisco.com`, and GitHub for version checks. No destination is added. |
+| **Tests gate** | ✅ Pass | The surviving verdict logic keeps its tests, and the new guard gets its own. The amended gate's requirement that analysis-flow changes be covered by tests running with no credentials and no network is met — the whole `test_api_flow.py` suite uses a fake client and touches no network. |
+| **Documentation gate** | ✅ Pass | FR-026 and FR-027 require `README.md` and `docs/index.html` to be corrected and mutually consistent within this same change. |
+| **Versioning & releases gate** | ✅ Pass | FR-037 sets `VERSION` to 4.0.0; the tag is pushed only after the PR merges. |
+| **Branching Strategy** | ✅ Pass | Work is on `007-decommission-ai-analyzer`, cut from `main`, reaching `main` by pull request. |
+
+**Result: no violations.** The Complexity Tracking section is therefore omitted.
+
+*Post-Phase 1 re-check: still passing. The design added one guard function and three contracts; it
+introduced no dependency, no new external destination, no new persisted data, and no change to the
+verdict path. See the re-check note at the end of this document.*
+
+## Project Structure
+
+### Documentation (this feature)
+
+```text
+specs/007-decommission-ai-analyzer/
+├── plan.md # This file
+├── research.md # Phase 0 output — 11 decisions
+├── data-model.md # Phase 1 output — entities that change, and those that must not
+├── quickstart.md # Phase 1 output — validation scenarios
+├── contracts/
+│ ├── cli-surface.md # The complete post-change command surface
+│ ├── config-file.md # Per-user configuration format and key set
+│ └── product-copy.md # The canonical tagline and description, and where each appears
+├── checklists/
+│ └── requirements.md # Spec quality checklist (from /speckit-specify)
+└── tasks.md # Phase 2 output (/speckit-tasks — NOT created here)
+```
+
+### Source Code (repository root)
+
+```text
+caia/
+├── __init__.py # EDIT docstring: drop "AI-driven"
+├── cli.py # EDIT remove 5 options; add removed-option guard; reword
+│ # description, --config and --yes help; simplify dispatch
+├── config.py # EDIT drop 3 AI keys, DEFAULTS, fuelix import; Cisco-only prompts
+├── analyzer.py # EDIT delete run() and the advisory path; run_api() -> run()
+├── inventory.py # EDIT delete build_combos() and expand_combo_ids()
+├── matching.py # EDIT drop the now-dead validate_alias parameter
+├── ui.py # EDIT _NAME and _SLOGAN
+├── psirt.py # EDIT docstring only (references the deleted siblings)
+├── html_report.py # KEEP untouched
+├── version.py # KEEP untouched
+├── fuelix.py # DELETE (217 lines)
+├── cisco.py # DELETE (198 lines)
+└── report.py # DELETE (61 lines)
+
+tests/
+├── test_api_flow.py # PRUNE frozen regression guard; remove 4 dry-run + 1 AI-key case
+├── test_cli.py # PRUNE ApiFlagTests -> removed-option guard tests
+├── test_config.py # PRUNE AI settings, prompts, and defaults
+├── test_folders.py # PRUNE WriteReportTests (Excel writer)
+├── test_banner.py # PRUNE product name assertion
+├── test_matching.py # KEEP untouched
+├── test_psirt.py # KEEP untouched
+├── test_html_report.py # KEEP untouched
+├── test_version.py # KEEP untouched
+└── test_extraction.py # DELETE (75 lines — CSAF extraction only)
+
+tools/install-test.sh # EDIT drop advisory-mode and dry-run steps
+docs/index.html # EDIT title, meta, hero, feature card, prerequisites, run steps
+README.md # EDIT the largest single edit; see research D11
+.env.example # EDIT Cisco variables only
+pyproject.toml # EDIT description
+VERSION # EDIT 3.0.0 -> 4.0.0
+```
+
+**Structure Decision**: Single-project CLI, unchanged. This feature adds no module and no
+directory; it deletes three modules and edits eight. The package layout, the console-script entry
+point, and the test layout all stay exactly as they are — a removal that reorganised the tree would
+make the behavioural-equivalence review harder for no benefit.
+
+## Phase 0 — Research
+
+See [research.md](./research.md). Eleven decisions, the load-bearing ones being:
+
+- **D1** — the removed-option guard must run before `parse_args`, because argparse gives `--help`
+ precedence over an unrecognised argument (verified empirically). This is the only new behaviour
+ in the feature.
+- **D2** — the guard matches option names exactly, so abbreviations and misspellings fall through
+ to argparse's ordinary unknown-argument error, satisfying FR-012 for free.
+- **D9** — behavioural equivalence is proven by capturing a report from the current `--api` path
+ before the change and diffing it against the post-change `caia` output for the same inputs.
+
+## Phase 1 — Design & Contracts
+
+- [data-model.md](./data-model.md) — the three entities that change shape, and an explicit list of
+ the entities that must **not** change (device, verdict, reason, advisory, report), since this is
+ a removal and the unchanged set is the part under threat.
+- [contracts/cli-surface.md](./contracts/cli-surface.md) — every surviving option, every removed
+ option with its refusal message, the precedence rule, and the exit-code table.
+- [contracts/config-file.md](./contracts/config-file.md) — the managed key set, the on-disk format,
+ and the read/write behaviour for files written by earlier versions.
+- [contracts/product-copy.md](./contracts/product-copy.md) — the canonical tagline and one-line
+ description with the exact surfaces each appears on, so FR-023's "verbatim" requirement is
+ checkable rather than aspirational.
+- [quickstart.md](./quickstart.md) — validation scenarios covering each user story, including the
+ before/after report diff that proves FR-006.
+
+## Post-Design Constitution Re-check
+
+Re-evaluated after Phase 1: **still no violations.**
+
+The design introduces one function (the removed-option guard), no dependency, no external
+destination, no persisted data, and no change to the verdict path. Two points were checked
+specifically because a removal can quietly breach a principle:
+
+- **Principle III** requires every capability to stay reachable from the command line. Removing the
+ dry run removes a capability rather than hiding one, which the amended constitution now
+ contemplates — the gate that depended on it was deleted in the same amendment. No surviving
+ capability becomes unreachable.
+- **Principle I's** minimal-dependency rule cuts both ways after a removal. Both surviving
+ dependencies were re-justified against surviving behaviour (research D7) rather than retained by
+ inertia; neither is now unused.
diff --git a/specs/007-decommission-ai-analyzer/quickstart.md b/specs/007-decommission-ai-analyzer/quickstart.md
new file mode 100644
index 0000000..8edcdb3
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/quickstart.md
@@ -0,0 +1,243 @@
+# Quickstart: Validating the Decommission
+
+**Feature**: [spec.md](./spec.md) | **Plan**: [plan.md](./plan.md) | **Date**: 2026-07-26
+
+Runnable scenarios that prove the feature works. Each maps to a user story. Scenario 0 must be done
+**before any code is deleted** — it is the baseline that proves the surviving check didn't move.
+
+Prerequisites: a source checkout, Python 3.9+, the test inventory used by the suite, and (for the
+live scenarios only) Cisco PSIRT credentials.
+
+---
+
+## Scenario 0 — Capture the equivalence baseline (do this first)
+
+**Proves**: FR-006 / SC-003 — the surviving check is unchanged. See research D9.
+
+On the **current** code, before deleting anything:
+
+```bash
+git stash list && git rev-parse --short HEAD > /tmp/caia-baseline-rev.txt
+python -m unittest tests.test_api_flow -v 2>&1 | tail -5
+```
+
+Then produce a golden report from the faked client the suite already uses, and keep it:
+
+```bash
+mkdir -p /tmp/caia-baseline && python -m unittest tests.test_api_flow -v > /tmp/caia-baseline/suite.txt 2>&1
+```
+
+Record the report the suite writes (its temp output dir is printed by the test harness), or generate
+one against a real inventory if you have credentials:
+
+```bash
+caia --api --inventory inventory/inventory.xlsx --output-dir /tmp/caia-baseline --no-update-check
+```
+
+**Expected**: a report at `/tmp/caia-baseline/impact_report_*.html`. Keep it; Scenario 1 diffs
+against it.
+
+---
+
+## Scenario 1 — One command, one answer (User Story 1)
+
+**Proves**: FR-001 to FR-007.
+
+```bash
+cd /path/to/folder/with/inventory.xlsx
+caia
+```
+
+**Expected**:
+- The inventory-driven check runs with no mode flag.
+- A new `output/impact_report_.html` is written and its path printed.
+- No `analysis_output_*.xlsx` anywhere.
+- No temp folder is created (previously announced as "Downloading CSAF files to: …").
+
+Then the equivalence diff against Scenario 0:
+
+```bash
+diff <(sed -E 's/[0-9]{8}_[0-9]{6}//g; s/Generated.*//' /tmp/caia-baseline/impact_report_*.html) \
+ <(sed -E 's/[0-9]{8}_[0-9]{6}//g; s/Generated.*//' output/impact_report_*.html)
+```
+
+**Expected**: no differences. Any diff other than the timestamp is a FR-006 violation and must be
+explained before merge.
+
+Network confirmation (FR-032 / SC-004) — run with the update check off and watch outbound hosts:
+
+**Expected**: connections to `id.cisco.com` and `apix.cisco.com` only. Nothing to `api.fuelix.ai`.
+
+---
+
+## Scenario 2 — One credential set (User Story 2)
+
+**Proves**: FR-016 to FR-020.
+
+```bash
+caia --config
+```
+
+**Expected**: prompts for the Cisco client ID (visible) and client secret (hidden, with capture
+confirmation), verification against Cisco, then a save. **No AI key or model prompt at any point.**
+
+Then check the file the save produced:
+
+```bash
+cat "$HOME/Library/Application Support/caia/config" # macOS
+```
+
+**Expected**: only `CISCO_CLIENT_ID` and `CISCO_CLIENT_SECRET`, a header naming the current product
+and only the Cisco overrides, and `-rw-------` permissions.
+
+Obsolete-value inertness (FR-019 / SC-007):
+
+```bash
+printf 'FUELIX_API_KEY=leftover\nFUELIX_MODEL=whatever\n' >> "$HOME/Library/Application Support/caia/config"
+FUELIX_API_KEY=alsoleftover caia --version
+```
+
+**Expected**: normal output. No error, no warning, no mention of either value.
+
+Uninstall disclosure and the `--yes` path (FR-014, FR-020) — in a throwaway environment:
+
+```bash
+caia --uninstall --yes
+```
+
+**Expected**: completes without a prompt; the preserved-config message names Cisco credentials, not
+"API key and model".
+
+---
+
+## Scenario 3 — Retired options refuse (User Story 3)
+
+**Proves**: FR-008 to FR-012 and the precedence rule.
+
+```bash
+for opt in --api --url=X --keep-temp --no-keep-temp --dry-run; do
+ caia "$opt" >/dev/null 2>&1; echo "$opt -> $?"
+done
+```
+
+**Expected**: every line reports `2`, and each message names the option and says what to run
+instead.
+
+Precedence — the cases argparse would otherwise get wrong:
+
+```bash
+caia --api --help; echo "rc=$?" # expect 2 + refusal, NOT help
+caia --help --api; echo "rc=$?" # expect 2 + refusal
+caia --uninstall --dry-run; echo "rc=$?" # expect 2 + refusal, MUST NOT uninstall
+```
+
+Resembling options still get the ordinary error (FR-012):
+
+```bash
+caia --dry; echo "rc=$?" # expect 2 + argparse's "unrecognized arguments"
+caia --urls; echo "rc=$?" # expect 2 + argparse's "unrecognized arguments"
+```
+
+**Expected**: these two say *unrecognized*, not *removed*.
+
+Help is clean (FR-009):
+
+```bash
+caia --help | grep -E -- "--api|--url|--keep-temp|--dry-run" && echo "FAIL" || echo "OK: no removed options listed"
+```
+
+---
+
+## Scenario 4 — Accurate self-description (User Story 4)
+
+**Proves**: FR-021 to FR-027 and SC-005 / SC-008 / SC-012.
+
+```bash
+grep -rniE "fuelix|agentic|ai-driven|ai-assisted|Impact Agent" \
+ caia/ tests/ tools/ docs/ README.md .env.example pyproject.toml
+```
+
+**Expected**: no matches.
+
+```bash
+caia --help | head -20
+```
+
+**Expected**: the banner shows *Cisco Advisory Impact Analyzer* and the canonical tagline; the
+description line is the canonical one-line description; `--config` help names Cisco credentials.
+
+Documentation structure (SC-012): open `README.md` and `docs/index.html` and confirm each presents
+exactly one way to run the tool — no mode-comparison table, no mode choice, no second report format
+— and that the only credential in the prerequisites is Cisco PSIRT access. Both must also carry the
+statement that the advisory-URL mode was removed.
+
+---
+
+## Scenario 5 — Governance matches the product (User Story 5)
+
+**Proves**: FR-028 to FR-031. Already satisfied on this branch by the v3.0.0 amendment.
+
+```bash
+grep -niE "fuelix|\bAI\b|dry.run|analysis_output|api\.fuelix|sec\.cloudapps" \
+ .specify/memory/constitution.md | grep -v "^[0-9]*:.*Sync Impact\|^[1-7][0-9]:"
+```
+
+**Expected**: matches only inside the Sync Impact Report comment block (the amendment history,
+which must name what it removed). Nothing in the principles, constraints, or gates.
+
+---
+
+## Scenario 6 — Nothing left behind (User Story 6)
+
+**Proves**: FR-032 to FR-038 and SC-010 / SC-011.
+
+```bash
+python -m unittest discover -s tests -v 2>&1 | tail -5
+```
+
+**Expected**: all pass. No test references the retired mode.
+
+```bash
+ls caia/fuelix.py caia/cisco.py caia/report.py tests/test_extraction.py 2>&1
+```
+
+**Expected**: all four report "No such file or directory".
+
+```bash
+cat VERSION
+python -c "import caia, tomllib; print(caia.__version__)"
+```
+
+**Expected**: `4.0.0` from both.
+
+Dependency re-justification (FR-035) — confirm each remaining dependency is still reachable from
+surviving code:
+
+```bash
+grep -rn "openpyxl\|dotenv" caia/
+```
+
+**Expected**: `openpyxl` in `inventory.py` (reading the workbook), `dotenv` in `config.py` (loading
+`.env`). Nothing orphaned.
+
+Clean-room install test (FR-034):
+
+```bash
+tools/install-test.sh
+```
+
+**Expected**: completes end to end, exercising install, `--help`, the credential-refusal path, and a
+run invoked with no mode option. No step references the retired mode.
+
+---
+
+## Definition of done
+
+| Scenario | User story | Blocking? |
+|---|---|---|
+| 0 + 1 diff | US1 | **Yes** — a diff is a FR-006 violation |
+| 2 | US2 | Yes |
+| 3 | US3 | Yes |
+| 4 | US4 | Yes |
+| 5 | US5 | Already done (v3.0.0 committed) |
+| 6 | US6 | Yes |
diff --git a/specs/007-decommission-ai-analyzer/research.md b/specs/007-decommission-ai-analyzer/research.md
new file mode 100644
index 0000000..3f3f6f2
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/research.md
@@ -0,0 +1,249 @@
+# Phase 0 Research: Decommission the FueliX AI Analyzer
+
+**Feature**: [spec.md](./spec.md) | **Plan**: [plan.md](./plan.md) | **Date**: 2026-07-26
+
+No `NEEDS CLARIFICATION` markers entered this phase — the spec's four `/speckit-clarify` decisions
+and eleven assumptions closed them. The research below is therefore codebase archaeology and
+mechanism design: what exactly comes out, what must not move, and how the one new behaviour works.
+
+---
+
+## D1 — The removed-option guard must run before `parse_args`
+
+**Decision**: Scan `sys.argv` for removed option names in `cli.main()` *before* the parser is built,
+and refuse there. Do not rely on argparse to report them.
+
+**Rationale**: FR-011 requires a removed option to outrank every other option, including `--help`.
+Argparse cannot express that. Verified empirically against a parser with `--api` simply absent:
+
+| Command line | argparse result | FR-011 requires |
+|---|---|---|
+| `--api --help` | exit **0**, prints help | exit 2, refusal naming `--api` |
+| `--help --api` | exit **0**, prints help | exit 2, refusal naming `--api` |
+| `--api` | exit 2, "unrecognized arguments" | exit 2, refusal naming `--api` |
+
+The reason is ordering: argparse's `help` action fires the moment it is encountered during parsing
+and calls `sys.exit(0)`, whereas unrecognised arguments are collected and only reported after the
+whole line is parsed. So `--help` always wins against a merely-absent option. Since `--dry-run` on
+an uninstall line is the case the clarification session specifically wanted to refuse
+(`caia --uninstall --dry-run` must not uninstall), leaving precedence to argparse is not an option.
+
+**Alternatives considered**:
+- *Register the removed options with `help=argparse.SUPPRESS` and a custom action.* Rejected: the
+ action still fires in parse order, so `--help` first on the line still wins, and it puts dead
+ options back into the parser the feature is trying to empty.
+- *Subclass `ArgumentParser.parse_known_args` to pre-filter.* Rejected: same ordering problem, more
+ machinery, and it hides the rule inside a subclass rather than stating it once in `main()`.
+- *Accept argparse's behaviour and weaken FR-011 to "except `--help`".* Rejected: it re-opens the
+ `--uninstall --dry-run` footgun, which is the whole reason the rule exists.
+
+---
+
+## D2 — The guard matches exact option names only
+
+**Decision**: The guard compares each argv element against the exact removed names — and, for
+value-taking options, the `--name=value` form — and nothing else. Abbreviations, misspellings, and
+near-misses fall through to argparse.
+
+**Rationale**: FR-012 requires an option that merely resembles a removed one to produce the ordinary
+unknown-argument error, so the removal notice keeps its meaning. Exact matching delivers this with
+no extra logic: `--dry` was a valid abbreviation of `--dry-run` before, and afterwards argparse
+reports it as unrecognised (verified: exit 2, generic message). Attempting to be helpful about
+abbreviations would mean re-implementing argparse's prefix matching against a list of options that
+no longer exist.
+
+**Removed names the guard recognises**: `--api`, `--url`, `--keep-temp`, `--no-keep-temp`,
+`--dry-run`, plus `--url=`. `--url` is the only one that took a value; the guard refuses on
+the option itself and never inspects the value.
+
+**Alternatives considered**: fuzzy/prefix matching (rejected — contradicts FR-012); matching only
+the first occurrence (rejected — no benefit, and the first removed option encountered is the one to
+name anyway).
+
+---
+
+## D3 — Reuse exit status 2; the message carries the distinction
+
+**Decision**: The guard exits with `2`, the status argparse already uses for argument errors.
+
+**Rationale**: Settled in clarification Q2. A removed option *is* an argument error, so a separate
+status would be a new permanent public contract in service of a message that exists to be
+temporary. Automation that needs to distinguish "retired" from "mistyped" reads the message, which
+always names the option. Verified that argparse's own unrecognised-argument path also exits 2, so
+the two cases stay consistent in status and differ only in text — which is exactly the intent.
+
+**Exit-code table is unchanged otherwise**: `0` success, `1` declined confirmation, `2` argument
+error (now including removed options), `3` version lookup failed, `4` update/uninstall failed,
+`130` interrupt.
+
+---
+
+## D4 — The update offer keys on the terminal check alone
+
+**Decision**: `_start_of_run_update_nudge` skips only when the update check is suppressed
+(`--no-update-check` / `CAIA_NO_UPDATE_CHECK`) or when stdin is not a TTY. The `args.url` condition
+is deleted, not replaced.
+
+**Rationale**: Settled in clarification Q3. The `args.url` clause was a proxy for "this is a scripted
+run"; the TTY check already covers that case directly, and the surviving analysis options
+(`--inventory`, `--sheet`, `--output-dir`) are ordinary inputs rather than signals of automation.
+An interactive user who keeps their inventory elsewhere is precisely the person who should be told
+an update exists.
+
+**Consequence to accept**: an interactive run that would previously have skipped the prompt (because
+it passed `--url`) now sees it. This is a visible behaviour change, but not to the surviving check —
+it is to the update nudge, which FR-006's equivalence requirement does not cover, and the prompt
+remains skippable and time-bounded.
+
+---
+
+## D5 — Obsolete configuration keys: ignored on read, dropped on save
+
+**Decision**: `config.KEYS` shrinks to `(CISCO_CLIENT_ID, CISCO_CLIENT_SECRET)` and `DEFAULTS`
+becomes empty. `load_file()` keeps parsing every `KEY=value` line it finds (it is a generic parser),
+but nothing ever asks it for an AI key, so those values are inert. `save()` writes only the keys in
+`KEYS`, so a file rewritten by the new version silently loses the obsolete lines.
+
+**Rationale**: Settled in the spec's assumptions. No migration step, no pruning pass, no warning:
+the file belongs to the user, and rewriting it during an ordinary run would be surprising. Because
+`save()` already builds its output from the `KEYS` tuple rather than from the loaded dict, the drop
+is a consequence of shrinking `KEYS` and needs no dedicated code — which is the cheapest possible
+implementation of FR-012 and the reason it can be a P2.
+
+**Note for implementation**: `DEFAULTS` currently imports `fuelix.DEFAULT_MODEL` and
+`fuelix.DEFAULT_BASE_URL`. Emptying `DEFAULTS` is what lets `config.py` drop its `fuelix` import —
+the last import of the deleted module outside `analyzer.py`.
+
+---
+
+## D6 — `run_api` becomes `run`; `_dry_run` and `validate_alias` go
+
+**Decision**: Delete `analyzer.run()` (the advisory flow) and rename `analyzer.run_api()` to
+`analyzer.run()`. Delete `analyzer._dry_run()`. Drop the `validate_alias` parameter from
+`matching.resolve_device()` and `matching.build_checks()`.
+
+**Rationale**: With one mode, `run_api` is a name that describes a distinction that no longer
+exists. `validate_alias=False` has exactly one caller — `_dry_run` — confirmed by grep; with the dry
+run gone the parameter is dead, and `tests/test_matching.py` never references it, so removing it
+costs nothing and satisfies the no-residue requirement (FR-032).
+
+**Care required**: `matching.py` is verdict logic governed by Principle IV. The parameter removal
+must be purely mechanical — delete the parameter and the branch that reads it, leaving the
+`validate_alias=True` behaviour as the only path. Any other edit to that module is out of scope for
+this feature.
+
+**Shared helpers in `analyzer.py` that must survive untouched**: `die`, `default_output_dir`,
+`ensure_dirs`, `_is_valid_inventory`, `_xlsx_candidates`, `resolve_inventory`, `_pick_inventory`,
+`_result_line`. These are used by both flows today; touching them while deleting the other flow is
+the single most likely way to breach FR-006.
+
+**Deleted from `analyzer.py`**: `CISCO_HOST`, `is_valid_cisco_url`, `prompt_for_url`,
+`download_and_extract`, `_dumps`, `assessment_text`, `_assessment_label`, `run`, `_dry_run`, and the
+`cisco`, `fuelix`, and `write_report` imports.
+
+---
+
+## D7 — Both runtime dependencies are re-justified and both stay
+
+**Decision**: Keep `openpyxl` and `python-dotenv`. Remove neither.
+
+**Rationale**: Principle I's amended text requires a dependency that outlives its purpose to be
+removed, so each was checked against surviving behaviour rather than retained by inertia.
+
+| Dependency | Was used for | Still needed because |
+|---|---|---|
+| `openpyxl` | reading the inventory **and** writing the Excel report | `inventory.load_inventory` reads the user's `.xlsx`; only the writer (`report.py`) is deleted |
+| `python-dotenv` | loading a working-folder `.env` | `.env` support is retained for automation (spec Assumptions); only its AI variables are withdrawn |
+
+**Alternative considered**: dropping `python-dotenv` and having automation use environment variables
+only. Rejected here because the spec places removing `.env` support out of scope (BRD-004 §5.2). It
+remains a reasonable follow-up once the single-mode tool has been in use.
+
+---
+
+## D8 — Test suite disposition
+
+**Decision**: One file deleted, five pruned, four untouched.
+
+| File | Action | Reason |
+|---|---|---|
+| `test_extraction.py` | **delete** (75 lines) | tests CSAF extraction in `cisco.py` only |
+| `test_api_flow.py` | prune 5 of 38 cases | drop the four dry-run cases and `test_ai_service_key_is_never_consulted`; **everything else frozen** (see D9) |
+| `test_cli.py` | prune + add | `ApiFlagTests` (6 cases) becomes removed-option guard tests; `UninstallTests` untouched — it covers the `--yes` path FR-014 preserves |
+| `test_config.py` | prune | AI key/model/base-URL resolution, defaults, and the FueliX prompt cases; the Cisco credential and verification cases are untouched |
+| `test_folders.py` | prune | `WriteReportTests` (Excel writer); `ResolveInventoryTests`, `PickerTests`, `OutputDirTests` untouched — they cover shared helpers |
+| `test_banner.py` | prune | the assertion on the old product name |
+| `test_matching.py`, `test_psirt.py`, `test_html_report.py`, `test_version.py` | **untouched** | no AI dependency; `test_html_report.py` in particular guards the surviving report |
+
+**New coverage required**: the guard refuses each of the five removed options; the guard outranks
+`--help` and `--uninstall`; a resembling option still gets the generic error; the guard exits 2; the
+guard runs before any inventory load.
+
+---
+
+## D9 — Proving behavioural equivalence (FR-006 / SC-003)
+
+**Decision**: Capture a baseline before deleting anything, and diff against it after.
+
+1. On the current code, run the `--api` path against a fixed inventory with a recorded/faked Cisco
+ client and keep the resulting HTML report plus the printed output.
+2. After the change, run `caia` against the same inventory with the same fake client.
+3. Diff the two reports ignoring only the generated-at timestamp and the filename, and diff the
+ printed output.
+
+**Rationale**: FR-006 is the requirement most likely to be violated by accident, because the
+deletion runs through shared code. A golden-report diff catches a changed verdict, a dropped
+column, a reordered row, or a reworded reason — none of which a unit test asserting "a report was
+written" would catch. `tests/test_api_flow.py` already builds devices through a `FakeClient`, so the
+baseline is reproducible without network or credentials.
+
+**Alternatives considered**: relying on the unit suite alone (rejected — it asserts properties, not
+the full artifact); a snapshot test committed to the repo (rejected for now — a 400-row HTML fixture
+is a maintenance burden beyond this feature; the diff is a one-time verification step recorded in
+[quickstart.md](./quickstart.md)).
+
+---
+
+## D10 — Canonical copy lives in one place per surface
+
+**Decision**: The tagline and the one-line description each have exactly one authoritative
+definition per artifact type: `ui._SLOGAN` for the banner, `pyproject.toml`'s `description` for
+package metadata, and literal text in `README.md` and `docs/index.html`. The contract file
+[contracts/product-copy.md](./contracts/product-copy.md) is the source both are copied from.
+
+**Rationale**: FR-023 requires verbatim reuse across four surfaces. Python code cannot share a
+string with `pyproject.toml` or with HTML, so "one constant" is not achievable across the whole set;
+what is achievable is one written source of truth plus a check that all four match it. The contract
+makes that check mechanical.
+
+**Note**: the banner appends ` · TELUS Digital`; the package description does not. That difference
+is deliberate and recorded in the contract so it is not "fixed" later by someone tidying up.
+
+---
+
+## D11 — Documentation restructure, not search-and-replace
+
+**Decision**: `README.md` is restructured, not patched. The mode-comparison table, the advisory-mode
+run section, the FueliX key section, and the AI troubleshooting entries are removed as units, and
+the remaining flow is re-sequenced as a single path: prerequisites → install → configure → run →
+read the report → update/uninstall.
+
+**Rationale**: 25 FueliX references are spread across sections that only exist because there were
+two modes. Deleting the references sentence-by-sentence would leave a document shaped around a
+choice the reader no longer has, which fails SC-012's structural test ("exactly one way to run the
+tool, no mode-comparison table, no mode-selection decision"). `docs/index.html` needs the same
+treatment on a smaller scale, including renumbering its steps once the "Get your FueliX API key"
+section is removed.
+
+**Surfaces and their edits**:
+
+| Surface | Edit |
+|---|---|
+| `README.md` | restructure as above; correct h1, tagline, prerequisites, flag table, maintainer module table; add the removal notice |
+| `docs/index.html` | ``, meta description, hero tagline, feature card, prerequisites, run steps, remove section 03 and renumber |
+| `.env.example` | Cisco variables only |
+| `pyproject.toml` | `description` |
+| `caia/__init__.py` | module docstring |
+| `caia/psirt.py` | docstring cross-reference to the deleted siblings |
+| `tools/install-test.sh` | drop advisory-mode and dry-run steps; keep install, help, credential-refusal, and a real run |
+| `VERSION` | `4.0.0` |
From ac38a333614aa6846be5abe0b6537683f010f400 Mon Sep 17 00:00:00 2001
From: Ali Bahaloo
Date: Sun, 26 Jul 2026 23:24:08 -0700
Subject: [PATCH 3/4] docs(007): dependency-ordered task list for the FueliX
decommission
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
46 tasks (T001-T046) grouped by user story: 2 setup, 4 foundational,
7 US1, 6 US2, 4 US3, 9 US4, 1 US5, 9 US6, 4 polish. 22 parallelizable.
Two ordering decisions carry the risk of this change:
- T001 captures the equivalence baseline before any source edit. Once the
advisory path is deleted this working tree cannot produce it again, and
it is the only evidence that proves FR-006 (the surviving check did not
move) beyond the unit suite's property assertions.
- The three doomed modules are deleted last, in US6, not in the
foundational phase. Phase 2 makes them unreachable, which is enough to
make every user-facing story true; keeping the files on disk until the
end means the old code path is still available to compare against if the
equivalence diff fails.
MVP is US1, which is demonstrable but deliberately not releasable — after
it alone the tool still prompts for an AI key and calls itself agentic.
No product code changes yet.
Co-Authored-By: Claude Opus 5
---
specs/007-decommission-ai-analyzer/tasks.md | 306 ++++++++++++++++++++
1 file changed, 306 insertions(+)
create mode 100644 specs/007-decommission-ai-analyzer/tasks.md
diff --git a/specs/007-decommission-ai-analyzer/tasks.md b/specs/007-decommission-ai-analyzer/tasks.md
new file mode 100644
index 0000000..935f01c
--- /dev/null
+++ b/specs/007-decommission-ai-analyzer/tasks.md
@@ -0,0 +1,306 @@
+---
+
+description: "Task list for decommissioning the FueliX AI analyzer"
+---
+
+# Tasks: Decommission the FueliX AI Analyzer
+
+**Input**: Design documents from `/specs/007-decommission-ai-analyzer/`
+
+**Prerequisites**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md),
+[data-model.md](./data-model.md), [contracts/](./contracts/), [quickstart.md](./quickstart.md)
+
+**Tests**: Test tasks are included and are **not optional** here. The constitution's Tests gate
+requires the logic behind every verdict to have tests, requires the suite to pass before merge, and
+requires analysis-flow changes to be covered by tests that run with no credentials and no network.
+FR-032 additionally requires the suite to contain no reference to the retired mode.
+
+**Organization**: Tasks are grouped by user story. Note that this is a *removal*: the stories are
+independently testable, but the product is not releasable until all of them land — a
+half-decommissioned tool would still describe itself as AI-driven. See Implementation Strategy.
+
+## Format: `[ID] [P?] [Story] Description`
+
+- **[P]**: Can run in parallel (different files, no dependency on an incomplete task)
+- **[Story]**: Which user story the task belongs to (US1–US6)
+- Exact file paths are given in every task
+
+## Path Conventions
+
+Single-project CLI. Package at `caia/`, tests at `tests/`, docs at `README.md` and `docs/`,
+packaging at `pyproject.toml` and `VERSION`. No directory is added or moved by this feature.
+
+---
+
+## Phase 1: Setup & Baseline
+
+**Purpose**: Capture the evidence needed to prove the surviving check did not move. Nothing here
+edits source.
+
+**⚠️ T001 MUST complete before any source file is edited.** Once the advisory path is deleted the
+baseline can no longer be produced from this working tree.
+
+- [ ] T001 Capture the pre-change equivalence baseline per [quickstart.md](./quickstart.md) Scenario 0: run the current `--api` path and keep the resulting report plus printed output at `/tmp/caia-baseline/`
+- [ ] T002 [P] Record the current full-suite result as the regression reference: `python -m unittest discover -s tests` output saved to `/tmp/caia-baseline/suite-before.txt`
+
+**Checkpoint**: A golden report and a known-green suite exist to diff against.
+
+---
+
+## Phase 2: Foundational (Blocking Prerequisites)
+
+**Purpose**: Sever every dependency on the AI code path. Until this is done the modules cannot be
+deleted and the configuration cannot be reshaped.
+
+**⚠️ CRITICAL**: No user story work can begin until this phase is complete.
+
+- [ ] T003 [P] Remove the advisory-driven analysis from `caia/analyzer.py`: delete `CISCO_HOST`, `is_valid_cisco_url`, `prompt_for_url`, `download_and_extract`, `_dumps`, `assessment_text`, `_assessment_label`, `run()`, `_dry_run()`, and the `cisco` / `fuelix` / `write_report` imports; rename `run_api()` to `run()`. Leave the shared helpers (`die`, `default_output_dir`, `ensure_dirs`, `_is_valid_inventory`, `_xlsx_candidates`, `resolve_inventory`, `_pick_inventory`, `_result_line`) **untouched** — research D6
+- [ ] T004 [P] Reduce the managed key set in `caia/config.py`: delete the `API_KEY` / `MODEL` / `BASE_URL` constants, set `KEYS` to the two Cisco keys, empty `DEFAULTS`, and drop the `fuelix` import — research D5, [contracts/config-file.md](./contracts/config-file.md) §2
+- [ ] T005 [P] Delete the AI-only inventory compaction from `caia/inventory.py`: `build_combos()` and `expand_combo_ids()`, and correct the module docstring that describes sending combos to the AI
+- [ ] T006 Drop the now-dead `validate_alias` parameter from `resolve_device()` and `build_checks()` in `caia/matching.py`, leaving the `True` behaviour as the only path. Mechanical only — no other change to this module (Principle IV). Depends on T003 removing its sole caller
+
+**Checkpoint**: The AI code path is unreachable and nothing outside the doomed modules imports them.
+
+---
+
+## Phase 3: User Story 1 - Run the check with one command (Priority: P1) 🎯 MVP
+
+**Goal**: `caia` with no options performs the inventory-driven check and writes the HTML report,
+producing byte-identical results to the previous `--api` path.
+
+**Independent Test**: Run `caia` in a folder with an inventory and configured credentials; a
+timestamped HTML report appears, and it diffs clean against the T001 baseline.
+
+- [ ] T007 [US1] Remove the five retired options from `build_parser()` in `caia/cli.py`: `--api`, `--url`, `--dry-run`, `--keep-temp`, `--no-keep-temp`
+- [ ] T008 [US1] Simplify dispatch in `caia/cli.py`: `cmd_run()` calls `analyzer.run(args)` directly; delete the `--api`-plus-`--url` mutual-exclusion branch and its exit-2 path
+- [ ] T009 [US1] Apply the update-offer rule in `_start_of_run_update_nudge()` in `caia/cli.py`: delete the `args.url` condition, keeping only the suppression check and the TTY check (FR-015, research D4)
+- [ ] T010 [US1] Replace the parser description in `caia/cli.py` with the canonical one-line description from [contracts/product-copy.md](./contracts/product-copy.md) §1
+- [ ] T011 [P] [US1] Prune `tests/test_api_flow.py`: remove the four dry-run cases and `test_ai_service_key_is_never_consulted`. **Change nothing else in this file** — the remaining 33 cases are the frozen regression guard for FR-006
+- [ ] T012 [US1] Replace `ApiFlagTests` in `tests/test_cli.py` with cases asserting that `caia` with no mode option dispatches to `analyzer.run`, and that the removed options are absent from the parser
+- [ ] T013 [US1] Run the equivalence diff from [quickstart.md](./quickstart.md) Scenario 1 against the T001 baseline. Any difference other than the timestamp is an FR-006 violation and blocks this story
+
+**Checkpoint**: `caia` alone produces the report, and it is provably the same report.
+
+---
+
+## Phase 4: User Story 2 - Set up with one credential set (Priority: P1)
+
+**Goal**: `caia --config` asks only for Cisco credentials; values from an earlier version are inert
+and disappear on the next save.
+
+**Independent Test**: Run `caia --config` on a machine whose stored configuration still holds AI
+settings; only Cisco values are prompted, and the saved file afterwards contains only the two
+managed keys with owner-only permissions.
+
+- [ ] T014 [US2] Remove the FueliX section from `run_config()` in `caia/config.py` — API key prompt, model selection, and the base-URL note in the completion output. Keep the Cisco flow, its verification, and its three outcomes exactly as they are (FR-017)
+- [ ] T015 [US2] Update the configuration screen title and the trailing hints in `caia/config.py` to name the current product and only the Cisco settings
+- [ ] T016 [US2] Rewrite the configuration file header comments emitted by `save()` in `caia/config.py` per [contracts/config-file.md](./contracts/config-file.md) §4
+- [ ] T017 [US2] Reword `_report_preserved_config()` in `caia/cli.py` so the post-uninstall message names Cisco credentials rather than "API key and model" (FR-020)
+- [ ] T018 [US2] Prune `tests/test_config.py`: remove the AI key/model/base-URL resolution cases, the defaults cases, and the FueliX prompt cases. Keep every Cisco credential, verification, and permissions case untouched
+- [ ] T019 [P] [US2] Add cases to `tests/test_config.py` proving obsolete keys are inert: a file containing `FUELIX_*` produces no error on read, and those lines are absent after a save (FR-018, FR-019)
+
+**Checkpoint**: One credential set, and upgrading users meet no error from what they already had.
+
+---
+
+## Phase 5: User Story 3 - A retired option says so (Priority: P2)
+
+**Goal**: Every withdrawn option refuses by name, before anything else happens, outranking even
+`--help` and `--uninstall`.
+
+**Independent Test**: Run the tool with each removed option in turn; each exits 2 with a message
+naming the option, and no analysis, report, or management action occurs.
+
+- [ ] T020 [US3] Add the removed-option registry and guard to `caia/cli.py` per [contracts/cli-surface.md](./contracts/cli-surface.md) §3–§4, invoked from `main()` **before** the banner and before the parser is built. Match option names exactly, including the `--url=` form; exit 2 (research D1, D2, D3)
+- [ ] T021 [US3] Add guard tests to `tests/test_cli.py`: each of the five removed options refuses with exit 2 and a message naming that option and stating what to run instead
+- [ ] T022 [P] [US3] Add precedence tests to `tests/test_cli.py`: `--api --help`, `--help --api`, and `--uninstall --dry-run` all refuse; assert specifically that the uninstall case performs no uninstall (FR-011)
+- [ ] T023 [P] [US3] Add a resembling-option test to `tests/test_cli.py`: `--dry` and `--urls` produce argparse's unrecognised-argument error, **not** the removal message (FR-012)
+
+**Checkpoint**: Old command lines fail informatively instead of confusingly or dangerously.
+
+---
+
+## Phase 6: User Story 4 - The tool describes itself accurately (Priority: P2)
+
+**Goal**: Every user-facing surface describes a deterministic Cisco-data tool under the new name,
+with no AI claim and one credential in the prerequisites.
+
+**Independent Test**: Search the product, docs, samples, and packaging for any AI reference or the
+old product name; nothing is found. `caia --help` shows the new name, tagline, and description.
+
+- [ ] T024 [P] [US4] Update `_NAME` and `_SLOGAN` in `caia/ui.py` to the canonical product name and tagline (with the ` · TELUS Digital` suffix) from [contracts/product-copy.md](./contracts/product-copy.md)
+- [ ] T025 [P] [US4] Update the `--config` and `--yes` help strings in `caia/cli.py` per [contracts/cli-surface.md](./contracts/cli-surface.md) §2 — `--yes` must state that it skips the uninstall confirmation (FR-014)
+- [ ] T026 [P] [US4] Update the package docstring in `caia/__init__.py` and the docstring cross-reference to the deleted siblings in `caia/psirt.py`
+- [ ] T027 [P] [US4] Update `description` in `pyproject.toml` to the canonical tagline (without the attribution suffix)
+- [ ] T028 [P] [US4] Rewrite `.env.example` to document only `CISCO_CLIENT_ID` and `CISCO_CLIENT_SECRET` (FR-035)
+- [ ] T029 [P] [US4] Restructure `README.md` per research D11: remove the mode-comparison table, the advisory-mode run section, the FueliX key section, and the AI troubleshooting entries as units; re-sequence to one path (prerequisites → install → configure → run → read the report → update/uninstall); correct the h1, tagline, prerequisites, flag table, and maintainer module table; add the removal notice (FR-027)
+- [ ] T030 [P] [US4] Restructure `docs/index.html`: ``, meta description, hero tagline, the "AI impact analysis" feature card, prerequisites, and run steps; remove the "Get your FueliX API key" section and renumber the remaining steps
+- [ ] T031 [P] [US4] Update the product-name assertion in `tests/test_banner.py` (it asserts the old name literally, so it fails until T024 lands — intentional)
+- [ ] T032 [US4] Verify `README.md` and `docs/index.html` agree with each other and with the product, and that each presents exactly one way to run the tool (FR-026, SC-012). Depends on T029 and T030
+
+**Checkpoint**: The tool's description matches what it does.
+
+---
+
+## Phase 7: User Story 5 - Governance matches the product (Priority: P2)
+
+**Goal**: The constitution describes the tool that exists.
+
+**Independent Test**: Search the constitution for AI-provider assumptions; matches appear only in
+the amendment history.
+
+- [ ] T033 [US5] Verify the committed constitution v3.0.0 satisfies FR-028 to FR-031 per [quickstart.md](./quickstart.md) Scenario 5. **No code change expected** — the amendment landed in commit `e043d41` as a prerequisite of this feature; this task confirms it and records the result
+
+**Checkpoint**: The Constitution Check in future planning is a meaningful gate.
+
+---
+
+## Phase 8: User Story 6 - Nothing left behind (Priority: P3)
+
+**Goal**: The retired mode is gone from the codebase, not merely unreachable.
+
+**Independent Test**: The deleted modules do not exist, the suite passes with no reference to the
+retired mode, and every declared dependency is reachable from surviving code.
+
+- [ ] T034 [P] [US6] Delete `caia/fuelix.py`
+- [ ] T035 [P] [US6] Delete `caia/cisco.py`
+- [ ] T036 [P] [US6] Delete `caia/report.py`
+- [ ] T037 [P] [US6] Delete `tests/test_extraction.py`
+- [ ] T038 [US6] Remove `WriteReportTests` from `tests/test_folders.py`, keeping `ResolveInventoryTests`, `PickerTests`, and `OutputDirTests` — they cover shared helpers that survive
+- [ ] T039 [US6] Reduce `tools/install-test.sh` to surviving flows: drop the advisory-mode checks and the dry-run steps; keep install, `--help`, the credential-refusal path, and a run invoked with no mode option (FR-034)
+- [ ] T040 [P] [US6] Set `VERSION` to `4.0.0` (FR-037)
+- [ ] T041 [US6] Confirm each runtime dependency in `pyproject.toml` is still reachable from surviving code — `openpyxl` from `caia/inventory.py`, `python-dotenv` from `caia/config.py` — and remove any that is not (FR-036, research D7)
+- [ ] T042 [US6] Run the residue search from [quickstart.md](./quickstart.md) Scenario 6 across `caia/`, `tests/`, `tools/`, `docs/`, `README.md`, `.env.example`, and `pyproject.toml` (SC-005)
+
+**Checkpoint**: The removal is complete rather than partial.
+
+---
+
+## Phase 9: Polish & Cross-Cutting Concerns
+
+- [ ] T043 Run the full test suite over `tests/` (`python -m unittest discover -s tests -v`) and diff the result against `/tmp/caia-baseline/suite-before.txt` from T002, accounting for exactly the cases removed by T011, T018, T037, and T038
+- [ ] T044 [P] Run `tools/install-test.sh` end to end and confirm every step applies to the surviving tool
+- [ ] T045 Walk every scenario in [quickstart.md](./quickstart.md) and record the outcome, including the network confirmation that no AI endpoint is contacted (SC-004)
+- [ ] T046 Re-check the change against the Constitution Check table in [plan.md](./plan.md), confirming all twelve gates still pass against the delivered code
+
+---
+
+## Dependencies & Execution Order
+
+### Phase Dependencies
+
+- **Setup (Phase 1)**: no dependencies. **T001 must precede every source edit in the repository.**
+- **Foundational (Phase 2)**: depends on Phase 1 — blocks all user stories
+- **US1 (Phase 3)**: depends on Phase 2
+- **US2 (Phase 4)**: depends on Phase 2 (specifically T004, which reshapes the same file)
+- **US3 (Phase 5)**: depends on T007 having removed the options from the parser
+- **US4 (Phase 6)**: depends on Phase 2 only; the copy edits are independent of the behaviour work
+- **US5 (Phase 7)**: no dependencies — already satisfied, verification only
+- **US6 (Phase 8)**: depends on Phase 2 **and** on US1 and US2 — `caia/fuelix.py` cannot be deleted until neither `analyzer.py` (T003) nor `config.py` (T004) imports it
+- **Polish (Phase 9)**: depends on all stories
+
+### Story Dependency Graph
+
+```text
+Phase 1 (baseline)
+ │
+Phase 2 (sever AI dependencies) ── T003, T004, T005 in parallel → T006
+ │
+ ├─→ US1 (P1) ─┐
+ ├─→ US2 (P1) ─┼─→ US6 (P3) ─→ Polish
+ ├─→ US4 (P2) ─┘ ▲
+ │ │
+ └─→ US3 (P2) ──────────┘ (needs T007 from US1)
+
+ US5 (P2) — already complete, verify at any point
+```
+
+### Within Each Story
+
+- Source changes before the tests that assert them, except `tests/test_banner.py` (T031), which
+ is expected to fail until T024 lands
+- `caia/cli.py` tasks are sequential among themselves — same file
+- Documentation verification (T032) after both documents are restructured
+
+### Parallel Opportunities
+
+- **Phase 1**: T002 alongside T001
+- **Phase 2**: T003, T004, T005 — three different modules, no shared symbols
+- **US1**: T011 (test pruning) alongside the `cli.py` sequence
+- **US2**: T019 alongside T018 once the prune lands
+- **US3**: T022 and T023 alongside T021 — independent cases in the same file, so coordinate if one
+ person is editing
+- **US4**: T024 through T031 are eight different files and can all run at once
+- **US6**: T034, T035, T036, T037, T040 — four deletions and a version bump, all independent
+- **Across stories**: US4 (copy) can proceed alongside US1/US2/US3 (behaviour) with no conflict
+
+---
+
+## Parallel Example: User Story 4
+
+```bash
+# Eight independent files — launch together:
+Task: "Update _NAME and _SLOGAN in caia/ui.py"
+Task: "Update --config and --yes help strings in caia/cli.py"
+Task: "Update package docstring in caia/__init__.py and cross-reference in caia/psirt.py"
+Task: "Update description in pyproject.toml"
+Task: "Rewrite .env.example for Cisco variables only"
+Task: "Restructure README.md per research D11"
+Task: "Restructure docs/index.html"
+Task: "Update the product-name assertion in tests/test_banner.py"
+```
+
+## Parallel Example: Phase 2
+
+```bash
+# Three modules, no shared symbols:
+Task: "Remove the advisory-driven analysis from caia/analyzer.py"
+Task: "Reduce the managed key set in caia/config.py"
+Task: "Delete build_combos and expand_combo_ids from caia/inventory.py"
+# then, once analyzer.py no longer calls it:
+Task: "Drop the validate_alias parameter from caia/matching.py"
+```
+
+---
+
+## Implementation Strategy
+
+### MVP scope
+
+**Phase 1 + Phase 2 + US1** is the demonstrable MVP: `caia` with no options produces the report and
+diffs clean against the baseline. At that point the tool works the way this feature intends.
+
+**But the MVP is not releasable.** After US1 alone the tool still prompts for an AI key, still
+describes itself as agentic, and still contains `fuelix.py`. A removal feature only pays off when
+it completes — so treat US1 as the checkpoint that proves the approach, not as a shipping boundary.
+The releasable set is US1 + US2 + US3 + US4 + US6.
+
+### Recommended order
+
+1. **Phase 1** — capture the baseline. Everything downstream depends on this evidence existing.
+2. **Phase 2** — sever the dependencies. Small, mechanical, and it unblocks everything.
+3. **US1** — then immediately run T013. If the report diffs clean here, the riskiest part of the
+ feature is behind you; if it does not, stop and find out why before deleting anything else.
+4. **US2 and US3** — the remaining behaviour changes.
+5. **US4** — copy and documentation; can be done in parallel by a second person from step 3 onward.
+6. **US6** — the deletions, last, once nothing imports the doomed modules.
+7. **Phase 9** — full validation, then bump and release.
+
+### Why the deletions come last
+
+`caia/fuelix.py`, `caia/cisco.py`, and `caia/report.py` are deleted in US6 rather than Phase 2 on
+purpose. Keeping them on disk while the rest of the change lands means that if T013's equivalence
+diff fails, the old code path is still available to compare against. They become unreachable in
+Phase 2 and disappear in Phase 8 — unreachable is enough to make every user-facing story true.
+
+---
+
+## Notes
+
+- 46 tasks: 2 setup, 4 foundational, 7 US1, 6 US2, 4 US3, 9 US4, 1 US5, 9 US6, 4 polish
+- T001 is a hard ordering constraint, not a suggestion — the baseline cannot be recreated later
+- T011's instruction to change nothing else in `tests/test_api_flow.py` is load-bearing: that file
+ is the evidence for FR-006
+- T006 and T031 are the two tasks most likely to be done carelessly — one touches verdict logic
+ under Principle IV, the other is a test that must fail before its partner lands
+- Commit after each story so a bisect can isolate a behavioural regression to one phase
From ec7a5837052f25cf29a712ffac58e98932ebec98 Mon Sep 17 00:00:00 2001
From: Ali Bahaloo
Date: Mon, 27 Jul 2026 08:05:55 -0700
Subject: [PATCH 4/4] =?UTF-8?q?feat(007)!:=20decommission=20the=20FueliX?=
=?UTF-8?q?=20AI=20analyzer=20=E2=80=94=20the=20Cisco=20API=20check=20is?=
=?UTF-8?q?=20the=20only=20mode=20(4.0.0)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`caia` with no options now does what `caia --api` used to do: check every firewall in the
inventory against Cisco's published advisory data and write one HTML impact report. The
advisory-driven, AI-assisted mode is removed entirely.
BREAKING CHANGE: five options are withdrawn (--api, --url, --dry-run, --keep-temp,
--no-keep-temp), the Excel report is gone, and the FueliX API key is no longer read from
any source. Passing a removed option refuses with a message naming it and what to run
instead; leftover FUELIX_* values in an existing config file or the environment are inert
and disappear the next time `--config` saves.
Removed: caia/fuelix.py, caia/cisco.py, caia/report.py, tests/test_extraction.py, the
advisory-run path and dry run in analyzer.py, the AI-only combo compaction in inventory.py,
the now-dead validate_alias parameter in matching.py.
Added: a removed-option guard in cli.py that runs before the parser is built. argparse gives
--help precedence over a merely-absent option, so without this `caia --api --help` would
print help and exit 0 and `caia --uninstall --dry-run` would really uninstall. Matching is
exact, so an abbreviation such as --dry still gets argparse's ordinary error and the removal
notice keeps its meaning.
Renamed the product to Cisco Advisory Impact Analyzer (command, distribution, and repository
names unchanged) and put every surface on one canonical tagline and description: banner,
help, package metadata, README, landing page.
Verification:
- the post-change report is byte-identical to a pre-change baseline except the run
timestamp — same devices, verdicts, reasons, request count, and exit code
- a live run against Cisco: 70 firewalls, 27 checks, 55 Impacted / 0 Not impacted /
15 Needs review, matching the BRD-003 feasibility spike
- 246 unit tests pass
- clean-room install on python:3.9-slim passes, including that --uninstall --dry-run
refuses and leaves the tool installed
Note: tests/test_config.py deliberately names the FUELIX_* keys to prove they are inert;
that behaviour cannot be tested without naming them.
Implements spec 007 (specs/007-decommission-ai-analyzer/), BRD-004, constitution v3.0.0.
Co-Authored-By: Claude Opus 5
---
.env.example | 19 +-
.gitignore | 4 -
README.md | 222 ++++++---------
VERSION | 2 +-
caia/__init__.py | 2 +-
caia/analyzer.py | 286 ++------------------
caia/cisco.py | 198 --------------
caia/cli.py | 99 ++++---
caia/config.py | 77 ++----
caia/fuelix.py | 217 ---------------
caia/inventory.py | 54 +---
caia/matching.py | 16 +-
caia/psirt.py | 4 +-
caia/report.py | 61 -----
caia/ui.py | 4 +-
docs/index.html | 73 +++--
pyproject.toml | 2 +-
specs/007-decommission-ai-analyzer/tasks.md | 92 +++----
tests/test_api_flow.py | 50 +---
tests/test_banner.py | 2 +-
tests/test_cli.py | 151 ++++++++---
tests/test_config.py | 126 ++++++---
tests/test_extraction.py | 75 -----
tests/test_folders.py | 23 +-
tools/install-test.sh | 74 +++--
25 files changed, 519 insertions(+), 1414 deletions(-)
delete mode 100644 caia/cisco.py
delete mode 100644 caia/fuelix.py
delete mode 100644 caia/report.py
delete mode 100644 tests/test_extraction.py
diff --git a/.env.example b/.env.example
index 477ad75..25cdc88 100644
--- a/.env.example
+++ b/.env.example
@@ -1,18 +1,13 @@
-# The normal way to set your key is: caia --config
-# (it stores these values per-user, so every run in any folder finds them).
+# The normal way to set your credentials is: caia --config
+# (it stores them per-user, so every run in any folder finds them).
#
# This file is only for automation or a local per-folder override: copy it to ".env" in the
# folder you run the tool from, and these values are loaded into the environment for that run.
# Environment variables take precedence over the stored per-user config.
#
-# Get your key from the Fuel iX Dev Portal: https://dev.fuelix.ai
-# -> Default project -> "..." -> View -> API keys -> copy the key.
+# Get your own client ID and secret from the Cisco PSIRT developer portal:
+# https://developer.cisco.com/psirt/ -> register an application -> copy both values.
+# One per person; the client ID is an identifier, the client secret is a secret.
-FUELIX_API_KEY=
-
-# Optional. The model the analyzer sends advisories to.
-# Change only if your org has a different model id enabled (see https://dev.fuelix.ai).
-FUELIX_MODEL=claude-sonnet-5
-
-# Optional. OpenAI-compatible base URL (only change if instructed).
-FUELIX_BASE_URL=https://api.fuelix.ai/v1
+CISCO_CLIENT_ID=
+CISCO_CLIENT_SECRET=
diff --git a/.gitignore b/.gitignore
index f4d1956..db55caa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,7 +14,6 @@ venv/
# Inputs / outputs
inventory.xlsx
-analysis_output_*.xlsx
impact_report_*.html
~$*.xlsx
@@ -22,9 +21,6 @@ impact_report_*.html
/inventory/
/output/
-# Temp CSAF downloads (created via tempfile)
-csaf_*/
-
# macOS
.DS_Store
diff --git a/README.md b/README.md
index 24c5082..3f2a20c 100644
--- a/README.md
+++ b/README.md
@@ -2,19 +2,18 @@
-
Cisco Advisory Impact Agent
+
Cisco Advisory Impact Analyzer
-
Agentic firewall impact analysis for Cisco security advisories
+
Fleet-wide firewall impact analysis, straight from Cisco
---
-Paste a Cisco **Event Response (ERP)** link and get back a spreadsheet telling you
-**which of your firewalls are affected**.
+Run one command in the folder holding your firewall inventory and get back an HTML report
+telling you **which of your firewalls Cisco lists as vulnerable, and to what**.
-Behind the scenes it finds every security advisory referenced by the ERP, downloads each
-advisory's official data (CSAF), and uses AI (via **FueliX**) to compare the affected
-products and software releases against **your firewall inventory**. The result is a
-timestamped Excel file listing, per advisory, the impacted firewalls.
+It asks Cisco's **PSIRT advisory API** — the same published data behind Cisco's public
+Software Checker — which advisories apply to each firewall's software release on its exact
+hardware model, and reports the answer. Every verdict comes from Cisco; nothing is inferred.
Installed as a **[uv](https://docs.astral.sh/uv/) tool**: one install command, then a single
`caia` command you can run from any folder. Works on **macOS,
@@ -28,14 +27,12 @@ Windows, and Linux**.
| - | ----------- | ------------- |
| 1 | **uv** (recommended) — or just **Python 3.9+** | It manages Python for you. Not sure if you have it? See [Get uv](#get-uv) below — check first, install only if needed. Can't install uv? See [Install without uv](#install-without-uv-python-only) — you only need Python. |
| 2 | **Git** | The uv install command below pulls the tool straight from GitHub, so Git must be installed first. Not sure if you have it? See [Get Git](#get-git) below. |
-| 3 | **A FueliX API key** — for advisory mode | Get yours from — each person needs their own (steps below). |
-| 4 | **Cisco PSIRT API credentials** — for inventory mode (`--api`) | A client ID and client secret from — each person needs their own. |
-| 5 | **Your firewall inventory** as an Excel `.xlsx` file in the folder you run from | You build this yourself (format below). |
-| 6 | **Internet access** to `sec.cloudapps.cisco.com` and `api.fuelix.ai`, plus `apix.cisco.com` and `id.cisco.com` for `--api` | Usually already available; corporate proxies can block these. |
+| 3 | **Cisco PSIRT API credentials** | A client ID and client secret from — each person needs their own (steps below). |
+| 4 | **Your firewall inventory** as an Excel `.xlsx` file in the folder you run from | You build this yourself (format below). |
+| 5 | **Internet access** to `apix.cisco.com` and `id.cisco.com` | Usually already available; corporate proxies can block these. |
-> Every credential is stored once, per user, via `caia --config`. You only need the ones
-> for the mode you use: **advisory mode** needs the FueliX key, **inventory mode** needs the
-> Cisco credentials. Each mode refuses to run without its own, and points you at `--config`.
+> Your credentials are stored once, per user, via `caia --config`. The tool refuses to run
+> without them and points you at `--config`.
### Get uv
@@ -140,15 +137,14 @@ repository to clone and no virtual environment to create or activate.
> no need to re-run `--config`. Note that `caia --update` cannot make this jump for you: the
> distribution name changed, so an old install has to be replaced with the command above.
-Then set your FueliX API key once (stored per-user, used from any folder):
+Then set your Cisco credentials once (stored per-user, used from any folder):
```bash
caia --config
```
-`--config` asks for your API key (typing is hidden) and lets you pick the AI model from a short
-list (default `claude-sonnet-5`). The values are saved to a per-user config file — see
-[Configuration](#configuration-your-api-key) below.
+`--config` asks for your Cisco client ID and client secret, checks them against Cisco, and
+saves them to a per-user config file — see [Configuration](#configuration) below.
---
@@ -181,7 +177,7 @@ py -m pip install .
This installs the `caia` command **inside that environment**. From then
on it behaves exactly like the uv install — use it as described in [Run it](#run-it) and
-[Configuration](#configuration-your-api-key) (`caia --config`, then
+[Configuration](#configuration) (`caia --config`, then
`caia` from your inventory folder).
**Notes for this path:**
@@ -193,41 +189,29 @@ on it behaves exactly like the uv install — use it as described in [Run it](#r
- **Updating is manual here.** `--update` is uv-specific and will report that uv wasn't found. To
update, download the newer ZIP and re-run `python -m pip install .` in your environment. Pass
`--no-update-check` to skip the start-of-run version check.
-- Everything else — `--config`, inventory discovery, the `output/` folder, `--dry-run`, and the
- analysis itself — works identically to the uv install.
+- Everything else — `--config`, inventory discovery, the `output/` folder, and the check
+ itself — works identically to the uv install.
---
## Run it
-There are **two modes**, and they answer opposite questions. Pick the one that matches what
-you're asking:
-
-| | Advisory mode (default) | Inventory mode (`--api`) |
-| --- | --- | --- |
-| **Question** | "Given this advisory, which of my firewalls are affected?" | "Given my inventory, which firewalls are vulnerable right now, and to what?" |
-| **You supply** | An ERP or advisory link | Nothing but the inventory |
-| **Answer comes from** | AI reading the advisory's text | Cisco's own published data — no AI |
-| **Output** | `analysis_output_.xlsx` | `impact_report_.html` |
-| **Needs** | FueliX API key | Cisco PSIRT client ID + secret |
-
-The output formats differ because the reports are read differently. The spreadsheet is one
-row per advisory, which suits a per-advisory review. The HTML report is one row per firewall
-with clickable advisory links, collapsible detail and filtering — which is what a
-fleet-wide worklist needs, and what a spreadsheet handles badly once a single device has
-dozens of advisories against it.
-
-### Inventory mode — check everything at once
-
-`cd` into the folder holding your inventory, then:
+`cd` into the folder holding your inventory `.xlsx`, then:
```bash
-caia --api
+caia
```
-No link needed. The tool asks Cisco which published advisories apply to each firewall's
-software release on its exact hardware model, then writes
-**`impact_report__