Skip to content

Close the gap from artifact to product: packet-first landing, offline flagship demo, governance parity, quick-win fixes - #1

Merged
CJud25 merged 18 commits into
mainfrom
feat/product-legibility-pass
Jul 23, 2026
Merged

Close the gap from artifact to product: packet-first landing, offline flagship demo, governance parity, quick-win fixes#1
CJud25 merged 18 commits into
mainfrom
feat/product-legibility-pass

Conversation

@CJud25

@CJud25 CJud25 commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the gap between an excellent artifact and an excellent product: makes the cited Opportunity Packet the first thing a stranger sees, gives the offline demo a resolvable flagship moment (bundled SYNTHETIC contract-facts + NIB/NPA samples), rewrites the self-contradicting Privacy & Governance page, and clears the quick-win frictions — without adding any score/ranking/feasibility semantics, without weakening the evidence/citation discipline, and without breaking a single existing test.

19 tasks (S0–S18) executed in order from C:/Users/cjudk/.claude/plans/reconradar-oneshot-2026-07-23.md, one gate-green commit per task:

  • S1 — Rename BD page H1 away from "Feasibility Scanner" (no score language)
  • S2 — Stop bracket-escaping cited source URLs (CommonMark autolinks)
  • S3 — Remove the dead "Planning controls" from the Governance page
  • S4 — Darken .small-note captions to WCAG AA
  • S5 — Widen the guided-demo Open button
  • S6 — Collapse raw scan JSON into an expander
  • S7 — De-noise the Cases table (lead with title/location, drop UUID/version)
  • S8 — Rewrite Privacy & Governance page to describe only the shipped product (ADR-024)
  • S9 — Correct stale doc references + expand CHANGELOG
  • S10 — Land on the Opportunity Packet tab first + re-cut the guided tour
  • S11 — Reconcile the offline/synthetic contract-facts pull failure at point of failure
  • S12 — Offline synthetic-example flagship facts (ADR-025) — bundled SYNTH-A2-0001 award resolves fully offline, honestly labeled, never API_RETRIEVED
  • S13 — Bundle a synthetic NIB/NPA sample + generator + offline scan affordance (ADR-026)
  • S14 — Fix the ACS geography year-guard (stale figures no longer survive a year change)
  • S15 — Log-and-distinguish the packet-path bare excepts (no more blaming internal bugs on "source unavailable")
  • S16 — Docker VOLUME + README/PILOT_RUNBOOK deployment clarity
  • S17 — Add ruff (lint) to CI
  • S18 — CI: pip-audit + container smoke boot + Python 3.12 matrix leg

Result: 582 passed (baseline 563 + 19 new tests), 0 failures; validate_demo_data.py exits 0; ruff check . exits 0. Full task-by-task detail in the shared worklog.

An adversarial QC + security review ran over this branch before this PR was opened; the one finding raised (docker build unverified locally) was confirmed as a documented, already-flagged fact rather than an actionable gap — see notes below.

Known gaps (flagged honestly, not hidden)

  • Docker not available in the execution sandbox. The VOLUME addition (S16) and the new CI container smoke-boot step (S18) were authored and manually reviewed for correctness but never run against a live Docker daemon locally — this PR's CI run (which does have Docker) is their first real exercise. Watch the docker build + smoke-boot step specifically.
  • pip-audit found 27 live advisories against pinned streamlit==1.46.1 and its transitive pillow dependency (fix versions 1.53.1/1.54.0 and 12.1.1–12.3.0 respectively). Left non-blocking (|| true) with an inline TODO(owner) per the plan's explicit fallback instructions — bumping Streamlit is a real, options-y decision against 563+ AppTest-pinned tests and does not belong in a CI-hardening task. This is [OWNER] step 6 in the plan.
  • One documented wording deviation in S12 (provenance line reworded to avoid a literal self-contradiction in the plan's own suggested text vs. its acceptance check) — ADR-025 reflects the wording actually shipped.

Not in this PR (owner-gated, per plan §7)

Merge decision, hosted-instance reboot/redeploy, DRAFT pilot success-criteria finalization, private-mirror sync decision, and prioritization of descoped "Next" follow-ups are all explicitly reserved for the owner.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk

CJud25 and others added 18 commits July 23, 2026 11:37
"BD Feasibility Scanner & Tracker" contradicted the packet-not-score
thesis in the first five seconds. New H1: "Opportunity Packet &
Public-Evidence Tracker". Nav page name and PAGE_RENDERERS key are
untouched (only the H1 changes) so the two-page contract tests and
demo_tour.target_page stay valid. DEMO_SCRIPT.md's operator Q&A about
the old name is replaced with an honest one-liner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…sh-corrupt the citation

FR API URLs legitimately contain "[]" in query params (e.g.
conditions[type][]=NOTICE); the blanket bracket-escape rendered visible
backslashes that broke paste-and-resolve in both the packet body and the
downloadable export. A clean absolute http(s) value with no whitespace/
angle-brackets/pipe now renders as a CommonMark autolink (<https://...>),
which preserves "[]()" literally and cannot itself carry an injection.
Applied to pl_activity's source_url/html_url lines and packet_export's
manifest reference column only -- titles, filenames, and every other
field stay on the existing fully-escaped renderer. New tests assert the
verbatim bracketed URL is present with no backslash escapes, in both the
packet body and the Source manifest, while a non-URL reference (e.g. an
uploaded filename) is still two-layer escaped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
The Scenario selectbox and Planning target slider only rendered on the
Governance (non-BD) page and changed nothing downstream -- both page
renderers immediately `del` them. Interactive controls that do nothing
invite a reviewer to distrust the trust page, and the 70-82 slider
visually resembled an ODLH knob the tool insists it never computes.
scenario/target_pct now stay unconditionally set to their prior defaults
so PAGE_RENDERERS(...) and the renderer signatures need no changes.
Extended the existing packet-pages-render AppTest to assert neither
widget renders on the Governance page in normal (non-pilot) mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
.small-note (#627D98 on the #F4F6F8 paper background) carries the
legal/evidence-caveat disclaimers site-wide and fell below WCAG AA for
small text. Swapped to #52606D, the slate already used for
.page-subtitle and the metric label, which clears AA. Single hex value
changed; no other rule touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…lips

_render_guided_demo's three sidebar buttons (Previous / Open / Next) split
the sidebar width evenly, clipping "Open" to "Ope/n" at 1440px -- the
first interactive element a new evaluator sees. Column ratios changed to
[1, 2, 1] so Open gets ~half the width; buttons, keys, callbacks, and
use_container_width are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
Every local scan dumped a developer-facing st.json payload right beside
the friendly status banner. The JSON is preserved for analysts who want
it, now tucked behind a collapsed expander instead of always-visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…UUID/version noise

_cases_frame led with the raw case_id UUID and always showed the
internal `version` column -- default-developer-table feel that undersold
the product (§6.2). Reordered to lead with title/location/state, then
team/role, then any of contract/service/headcount/start/job-families that
actually has data across the current caseload (all-empty optional columns
are dropped); case_id moves to the last column instead of the first,
widest one. `version` is dropped from the displayed frame entirely (still
on the Case object for any code that needs it). `state` semantics
(repo.displayed_state(case).value) are untouched. New unit tests cover
both the reorder and the empty-column suppression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…g to match the shipped product (ADR-024)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
… CHANGELOG

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…r to match

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…at the point of failure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…d offline facts (ADR-025)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…dance (ADR-026)

Generate data/samples/sample_nib_npa.xlsx (scripts/make_sample_nib_npa.py) so
the tracker's NIB_NPA lane -- previously only satisfiable by a real workbook
upload -- can be driven offline. _render_scan gains an in-form checkbox
("Use the bundled SYNTHETIC NIB/NPA example instead of an upload") that
forces SourceKind.NIB_NPA and feeds the bundled bytes through the unchanged
WorkbookScanner.run_scan path; the attestation checkbox is still required.

Primary proof is a unit test parsing the actual committed sample through the
real NIB_NPA schema (3 PARSED rows); a secondary AppTest seeds a case
against the isolated ledger and confirms the checkbox renders wired with no
exception. DEMO_SCRIPT.md Act 4 and PILOT_RUNBOOK.md both note the new
offline lane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…-aware reuse guard)

The stored geography result's reuse guard matched on county+state only, so
changing the ACS vintage year without re-pulling left a stale prior-year
figure attached -- contradicting README.md's "editing an input detaches any
stale result built on it" invariant. Stamp the stored result with the pull
year and require it to match the current op_packet_year input before reuse.

New AppTest confirms the bug (verified failing pre-fix) and the fix: pull at
2022, change the year to 2019 without re-pulling, assert the section falls
back to "Not yet retrieved" instead of showing the stale figure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…ing them as source outages

Add a module logger (logging.getLogger(__name__)) to bd_page.py. All eight
packet-path except Exception: handlers now call logger.exception(<static
context>) before showing the operator message (never the URL, key, or PII).
Because ConnectorError -- the genuine upstream-outage case -- is already
caught first in every one of these blocks, the bare except is by definition
an unexpected internal error, so the five handlers that previously said "The
public source may be unavailable" (contract facts x2, subawards, ACS,
Federal Register) are reworded to say an internal error was logged instead
of blaming the source. The three handlers that already named the real cause
(handoff, directory upload, PL workbook) keep their message, gaining only
the logger.exception call.

New offline test drives a monkeypatched ValueError through the contract
facts pull and asserts both the operator-facing message (no "source may be
unavailable", mentions "logged") and the actual logger.exception call via
caplog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…dows execution-policy notes in the README

Dockerfile: the SQLite case ledger (data/runtime/tens_hq.sqlite3) is
.dockerignore'd, so it is absent from the built image. Create the directory
and chown it to the runtime user (uid 10001) WHILE STILL ROOT, then declare
it VOLUME ["/app/data/runtime"] -- a bare VOLUME on a path that doesn't yet
exist would make Docker auto-create it root-owned, which the runtime user
could not write, silently breaking the ledger on any container recreation
that didn't already have a working writable mount by luck.

README.md Quickstart: documents the execution-policy-safe invocation
(`powershell -ExecutionPolicy Bypass -File .\run_demo.ps1`) for the default
Windows 11 policy that blocks `.\run_demo.ps1` directly, and adds a Hosting
note surfacing the single-user/unauthenticated ledger boundary (already
documented deep in ARCHITECTURE.md) at the point a deployer actually reads
the quickstart.

docs/PILOT_RUNBOOK.md: documents the persistent-volume `docker run` form so
a container recreation does not silently lose the ledger.

Note: `docker build` could not be executed in this local sandboxed
environment (no Docker CLI/daemon installed here). The Dockerfile change is
a careful manual syntax review plus a direct transcription of the plan's own
verified snippet; the full pytest + validate_demo_data.py gate stays green.
`docker build` itself runs in CI and at the final pre-PR gate a later stage
owns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
Pin ruff==0.15.22 in requirements-dev.txt and pyproject.toml's dev extra;
add a [tool.ruff]/[tool.ruff.lint] config (line-length 100, target py311,
select E/F/W/I) and a "Lint (ruff)" CI step before the pytest step.

E501 (line-too-long) is deliberately excluded via ignore = ["E501"]: 515 of
585 initial findings were this one rule, spanning nearly every module in a
codebase whose established style favors long single-line comprehensions and
assertions over hard-wrapping -- enforcing it would force a sweeping,
high-risk reformat rather than a surgical lint gate (the plan's own
risk-mitigation guidance: narrow the noisy rule rather than mass-reformat).
`ruff format --check` is not added either, for the same reason (48 files
would reformat) -- the plan only adds it "if it is already clean".

Every other finding was fixed for real: 13 unused imports and 32 unsorted
import blocks (mechanical, verified by AST name-set diff against every
touched file -- nothing added or dropped beyond the intended prunes), 14
`l`-named comprehension variables renamed to `lead`/`line` in two test
files, and 2 unused local variables (one was a genuine dead computation in
synthetic.py; the other, in case_store.py, was an existence-check call kept
in place with its now-intentionally-discarded return value documented).

Also added `# noqa: E402` to the three scripts/entry points whose sys.path
mutation must precede their src-layout imports (app.py, generate_demo_data.py,
validate_demo_data.py) -- reordering was not an option there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
test-and-build now runs as a matrix over python-version ["3.11", "3.12"] so
the numpy>=2.5.1 branch real deployments install (requirements.txt's
python_version marker) is exercised in CI, not just 3.11/numpy 2.4.6.

Pin pip-audit==2.10.1 (requirements-dev.txt + pyproject.toml dev extra) and
add a "Dependency vulnerability scan" step. It is intentionally non-blocking
(`|| true`) with an inline TODO(owner) comment: pip-audit currently flags 27
known advisories across the pinned streamlit==1.46.1 and its transitive
pillow dependency, and every fix requires a streamlit major-version bump --
a runtime-behavior change this CI-hardening task does not make unilaterally
against a suite where 563+ tests are AppTest-pinned to this exact version.
Per the plan, this is an owner-gated triage decision (§7 [OWNER] item 6).

Add a container smoke boot after `docker build`: run the built image
detached, poll /_stcore/health for up to ~30s, fail the step if it never
reports healthy, then always stop/remove the container. A comment notes the
health endpoint does not execute app.py or open the case ledger, so it
proves the container boots and serves -- not a substitute for the
Dockerfile VOLUME's writable-ownership review from the prior commit.

pip-audit, docker build, and the smoke boot all run once, on the 3.11 leg
only -- they exercise the shipped artifact, not the interpreter matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
@CJud25
CJud25 merged commit b10e23d into main Jul 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant