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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 93 additions & 49 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,60 @@
<!--
Sync Impact Report
==================
Version change: 2.2.1 → 3.0.0
Rationale: Enacted as a prerequisite of BRD-004 (decommission the FueliX AI analyzer). The
advisory-driven, AI-assisted analysis is removed from the product, leaving the
inventory-driven Cisco PSIRT API check as the tool's only behaviour. This amendment
removes every rule written around an AI provider: Principle I no longer admits an AI
endpoint as permitted network access, Principle IV drops the AI-normalization clause
and collapses to a single undecided label, Principle V replaces the AI-endpoint data
minimization rule with a stricter one bounded to Cisco, the "AI provider" constraint is
deleted outright, and the dry-run quality gate is deleted because the capability it
depended on is removed. Removing principles and a governance gate is a backward-
incompatible redefinition, so the bump is MAJOR. The product is also renamed "Cisco
Advisory Impact Agent" → "Cisco Advisory Impact Analyzer" so the name no longer implies
agentic AI; the `caia` command, the `caia-cli` distribution, and the repository name are
unchanged. This constitution's own version is independent of the product version, which
moves to 4.0.0 under BRD-004.

Modified principles:
- I. Standard-Library-First & Minimal Dependencies (drops FueliX from permitted network access;
restates openpyxl's purpose as reading the inventory, not writing Excel reports)
- III. CLI-First, Scriptable Interface (illustrative flag list cites only surviving options)
- IV. Conservative & Traceable Analysis (drops the AI-normalization requirement; single
`Needs review` label replaces the per-mode label choice; traceability narrowed to the PSIRT
advisory API and its catalogues)
- V. Secrets Hygiene & Data Locality (the AI-endpoint minimization rule is replaced by a
stricter, Cisco-bounded rule: only the derived family, platform and release may leave the
machine)
Added sections: (none)
Version change: 3.0.0 → 3.1.0
Rationale: Enacted after product 4.1.0, which extracted the assessment into `caia/assessment.py`
and established four modules — `assessment`, `matching`, `psirt`, `inventory` — as a
shared surface callable without a terminal. That boundary exists to protect the safety
property in Principle IV: it is implemented in this code exactly once, and a second
consumer that reimplements it produces a second, divergent answer about the same
firewall. Until now the boundary was recorded only in a module docstring and enforced
only by one test, so nothing prevented a future change from reintroducing a print, a
prompt, or a `sys.exit` into the surface and quietly making it terminal-only again.
This amendment adds Principle VI to state the boundary, names the surface as a released
interface in Technology & Data Constraints, and extends three quality gates to enforce
it (tests for the headless property, versioning for its public contract, documentation
for the README table that describes it). A new principle plus materially expanded
guidance, with no principle removed or redefined, so the bump is MINOR. No product
behaviour changes; the product version is unaffected and remains 4.1.0.

Modified principles: (none — Principle IV is referenced by the new principle but not restated)
Added sections:
- VI. Headless Shared Surface (new Core Principle)
Modified sections:
- Technology & Data Constraints (Source of truth: ERP pages and CSAF JSON removed; Inputs/
outputs: the Excel report removed, one HTML report remains; Networking: `sec.cloudapps.cisco.com`
and `api.fuelix.ai` removed as contacted destinations)
- Development Workflow & Quality Gates (Tests: CSAF/ERP parsing replaced by the surviving
analysis logic, and the unit suite is named as the credential-free verification path that the
removed dry run previously provided)
- Governance (product name)
Removed sections:
- Technology & Data Constraints → "AI provider" bullet (FueliX Chat Completions endpoint)
- Development Workflow & Quality Gates → "Dry-run" gate (the capability is removed by BRD-004;
deleted rather than reworded)
- Technology & Data Constraints (new "Consumers" bullet: the package is both the `caia` command
and an importable library; the shared surface is a released interface, while modules outside
it carry no such guarantee)
- Development Workflow & Quality Gates → Tests (the headless property of the shared surface must
be covered: prints nothing, does not exit, does not mutate the caller's inventory, and raises
rather than returning verdicts when Cisco cannot be reached at all)
- Development Workflow & Quality Gates → Review (Principle VI added to the principles a pull
request must specifically confirm)
- Development Workflow & Quality Gates → Documentation (the README shared-surface table must
stay accurate within the same pull request)
- Development Workflow & Quality Gates → Versioning & releases (a change to the shared surface's
public contract must drive the version bump, because other products pin a released version)
Removed sections: (none)

Templates requiring updates:
- .specify/templates/plan-template.md ✅ aligned (Constitution Check resolves gates dynamically)
- .specify/templates/spec-template.md ✅ aligned (no constitution references)
- .specify/templates/tasks-template.md ✅ aligned (no constitution references)
- .specify/templates/checklist-template.md ✅ aligned (no constitution references)
- .claude/skills/speckit-*/SKILL.md ✅ aligned (no AI-provider or dry-run references)
- .claude/skills/speckit-*/SKILL.md ✅ aligned (reference the constitution generically, not by
principle)
- README.md ✅ aligned (the maintainers section already carries the shared-surface table this
amendment now requires)

Follow-up TODOs:
- README.md and docs/index.html still describe the retired AI mode and the product's former
name. They are corrected by the BRD-004 implementation, not by this amendment; the
Documentation gate below is what will hold that PR to it.
Follow-up TODOs: (none)

Previous version 3.0.0 (2026-07-26): enacted as a prerequisite of BRD-004 (decommission the
FueliX AI analyzer). Every rule written around an AI provider was removed — Principle I dropped
the AI endpoint from permitted network access, Principle IV dropped the AI-normalization clause
and collapsed to a single `Needs review` label, Principle V replaced the AI-endpoint data
minimization rule with a stricter Cisco-bounded one, the "AI provider" constraint was deleted, and
the dry-run quality gate was deleted with the capability it depended on. The product was renamed
"Cisco Advisory Impact Agent" → "Cisco Advisory Impact Analyzer". Removing principles and a
governance gate is a backward-incompatible redefinition. MAJOR.
Previous version 2.2.1 (2026-07-25): the GitHub repository and the distribution were renamed to
`caia-cli`; only the example install command changed. PATCH.
Previous version 2.2.0 (2026-07-25): enacted as a prerequisite of spec 006 (inventory-driven
Expand Down Expand Up @@ -158,6 +160,33 @@ a personal credential; leaking either is a direct harm. Bounding the disclosure
values means an observer of the tool's traffic learns what software is being asked about, but
nothing about who runs it or where.

### VI. Headless Shared Surface
The modules that produce a verdict — `assessment`, `matching`, `psirt`, and `inventory` — are this
package's **shared surface** and MUST remain callable without a terminal. They MUST NOT print,
prompt, consult the current working folder, write a file, or exit the process; the caller supplies
the credentials, the inventory, and whatever progress reporting it wants. Terminal behaviour —
prompts, colour, progress output, inventory discovery, report writing, exit codes — belongs to
`cli`, `analyzer`, `ui`, `config`, `version`, and `html_report`, which are outside the shared
surface and MUST NOT be required by any consumer other than this CLI. Anything that consumes this
package MUST reuse the shared surface rather than reimplement any part of it. The surface's public
contract — `analyze`, `load_catalogues`, `families_in`, `describe_check`, the verdict vocabulary
and reason kinds, the `psirt` client and its error types, `load_inventory`, and the shape of a
device record — MUST NOT change incompatibly without a MAJOR product version bump. A shared-surface
function MUST report a failure to *reach* Cisco as an error rather than as a set of undecided
verdicts, so a caller can always tell "we could not ask" apart from "we asked and could not decide".

Rationale: the safety property in Principle IV is implemented in this code exactly once. A second
consumer that reimplements release normalization, platform resolution, or the verdict rules creates
a second, divergent answer about the same firewall — and that divergence reaches a customer as two
products contradicting each other about their own security posture. Keeping the surface headless is
what makes reuse possible at all: logic that prints, prompts, or exits the process cannot be called
by anything but a terminal, so the pressure to reimplement it comes from the coupling rather than
from the consumer. This does not weaken Principle III — every capability remains reachable from the
command line; the requirement is that the verdict logic must not *depend* on one. The
error-versus-undecided rule exists because an empty catalogue turns every device into an unmapped
model: a caller handed verdicts instead of an error would publish a report that reads as a customer
data-quality problem when the real cause was a rejected credential.

## Technology & Data Constraints

- Language/runtime: Python 3.9 or newer.
Expand Down Expand Up @@ -188,6 +217,11 @@ nothing about who runs it or where.
git source (and, like any installer, may fetch build/runtime dependencies from the Python
package index); the tool orchestrates this rather than overlaying files in place. Adding
any further external endpoint the analyzer itself contacts remains a reviewed change.
- Consumers: this package is both the `caia` command and an importable library. The shared surface
defined in Principle VI is therefore a **released interface** — a consumer depends on a published
release of it, never on a branch — so that surface MUST be documented in `README.md` and its
changes MUST drive the version bump (see the versioning gate below). Modules outside the shared
surface carry no such guarantee and MAY be reorganised freely.

## Branching Strategy

Expand All @@ -211,25 +245,35 @@ constitutional compliance is enforced.
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.
by tests that run with no credentials and no network access. The headless property of the shared
surface (Principle VI) MUST itself be covered by tests: that it prints nothing, does not exit the
process, does not mutate the caller's inventory, and raises rather than returning verdicts when
Cisco cannot be reached at all.
- 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).
Principle IV (conservative, traceable analysis), Principle V (secrets/data
handling), and Principle VI (a change inside the shared surface MUST NOT introduce output, a
prompt, working-folder access, or a process exit). Any new runtime dependency MUST be explicitly
justified (Principle I).
- Documentation: the `README.md` and the GitHub Pages landing page (`docs/index.html`)
MUST stay accurate for the uv install, run, update, and configuration steps whenever
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. Neither may describe a capability the tool does not have.
The `README.md` shared-surface table MUST stay accurate: a module added to or removed from the
surface, or a change to its public contract, MUST be reflected there in the same pull request.
- 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
MUST refuse to publish when the pushed tag does not match `VERSION`. The product version is
single-sourced from `VERSION` into the package metadata at build time, so the committed
version, the git tag, and the version the installed tool reports never drift. Bumping
`VERSION` is an ordinary change that reaches `main` through a pull request (see Branching
Strategy), and the tag is pushed only after that PR merges. This product version is
independent of this constitution's own version. See `CONTRIBUTING.md` for the release runbook.
Strategy), and the tag is pushed only after that PR merges. A change to the shared surface's
public contract (Principle VI) MUST be reflected in the bump — MINOR for an addition, MAJOR for
an incompatible change — because another product pins a released version of that contract. This
product version is independent of this constitution's own version. See `CONTRIBUTING.md` for the
release runbook.

## Governance

Expand All @@ -248,4 +292,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**: 3.0.0 | **Ratified**: 2026-07-14 | **Last Amended**: 2026-07-26
**Version**: 3.1.0 | **Ratified**: 2026-07-14 | **Last Amended**: 2026-07-27
25 changes: 25 additions & 0 deletions tests/test_assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,31 @@ def test_nothing_is_printed(self):
assessment.analyze(INVENTORY, FakeClient())
self.assertEqual(buf.getvalue(), "")

def test_the_process_is_never_exited(self):
"""The terminal flow calls `die()` on these paths; the shared surface must not.

Asserted explicitly rather than left to a test erroring out, because a
`SystemExit` reintroduced here would take a consumer's whole process down
(Constitution VI).
"""
cases = (
("catalogue auth failure",
FakeClient(catalogue_error=psirt.PsirtAuthError("rejected"))),
("catalogue unreachable",
FakeClient(catalogue_error=psirt.PsirtUnreachableError("unreachable"))),
("failure before any answer", FakeClient(fail_after=0)),
("failure part-way through", FakeClient(fail_after=1)),
("clean run", FakeClient()),
)
for label, client in cases:
with self.subTest(label):
try:
assessment.analyze(INVENTORY, client)
except SystemExit as exc: # pragma: no cover - the failure being guarded
self.fail(f"analyze() exited the process on {label}: {exc}")
except psirt.PsirtError:
pass # raising is correct here; exiting is not


class CallbackTests(unittest.TestCase):
"""`on_plan` and `on_progress` are what a caller builds a progress display from."""
Expand Down
Loading