IPO-006..010: factor scoring, dashboard, screener orchestration, enrichment, and the fail-closed AI extraction agent - #108
Conversation
Pure refactor, behavior identical. The sprint contract names the scoring modules backend/ipo/scoring/score_model.py and recommendation.py, so move the IPO-001 scorecard/verdict modules there ahead of the factor-derivation and caution-flag work: - git mv backend/ipo/scorecard.py -> backend/ipo/scoring/score_model.py - git mv backend/ipo/verdict.py -> backend/ipo/scoring/recommendation.py - new backend/ipo/scoring/__init__.py package facade - update the four import sites (backend.ipo facade, domain repository, two focused test modules); external callers keep importing score_ipo / build_recommendation from backend.ipo unchanged - update the filename-set assertion in tests/test_ipo_contract_policy.py - fix module paths in ipo-001 design doc and ipo-screener LLD Gates: full pytest (1517 passed, coverage 89.55%), ruff, mypy, compileall all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Additive migration + ORM for everything the IPO-6..10 screener persists: - new table ipo_extraction_proposals (IPO-010 review queue): AI-proposed extraction payloads with page citations, confidence, verifier notes, agent/model provenance, and a fail-closed review lifecycle encoded in CHECK constraints (pending rows carry no reviewer; approved rows must link the resulting immutable manual-extraction revision) - new table ipo_enrichment_signals (IPO-009): low-confidence SerpAPI observations with query/capture provenance, conservative parsed_value, quarantined marker, and a stamped source_policy - ipo_recommendations: widen ck_ipo_recommendations_type with the fourth 'Insufficient verified data' verdict type; add caution_flags_json (server-default empty list for legacy ipo-001-v1 rows) - ipo_scores: add nullable inputs_fingerprint (sha-256 of the exact evidence scored) as the idempotency anchor for run_ipo_screener - storage repository helpers for proposals, enrichment signals, and the latest-subscription read (SQL stays inside backend/storage) - downgrade refuses whenever any screener artifact exists, matching the ipo002..005 data-preserving pattern Test co-updates in the same commit per the repo's DB rule: both hardcoded table-name sets, new index/FK/column assertions, the ipo006 downgrade- refusal test, and the contract-policy documentation frozensets. Gates: full pytest (1518 passed, coverage 89.35%), ruff, mypy, compileall all green; ORM/Alembic parity holds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The factor-derivation layer the IPO-001 design deferred, plus the hard red-line policy on top of it: - backend/ipo/scoring/factor_derivation.py: pure derive_score_input() mapping ratio receipts + manual-extraction evidence + subscription snapshot + (optional) enrichment signals into the seven 0-100 FactorAssessments. Versioned Decimal band tables (half-open bounds), the None-vs-0 rule (missing evidence vs known-weak evidence), peer- median valuation premiums, and deterministic evidence-bearing reason strings with provenance (formula version, extraction id, sha256). - backend/ipo/scoring/caution_flags.py: the seven hard caution flags as a fixed-order report with TRIGGERED / NOT_TRIGGERED / NOT_EVALUABLE outcomes - absent evidence is reported, never guessed. Litigation reads only collector-recorded keyword matches from non-quarantined web signals. - backend/ipo/scoring/recommendation.py: new 'Insufficient verified data' type on the missing-critical branch; any triggered flag forces Not Recommended regardless of score with the flag named first in reasons; precedence missing-critical > flags > score bands. - backend/ipo/models.py: IpoCautionFlag/Report/Status, enrichment signal enum + detached record, IpoRecommendationResult.caution_flags (+ to_dict). - repository.evaluate_issue gains caution_flags / inputs_fingerprint / model_version keyword args (defaults keep IPO-001 callers identical) and round-trips the flag report through caution_flags_json. Tests first (TDD): tests/test_ipo_factor_derivation.py (every band boundary, None-vs-0 table, provenance strings), tests/ test_ipo_caution_flags.py (7 flags x 3 outcomes, near-close window, quarantine discipline), extended verdict + repository + facade tests. Gates: full pytest (1571 passed, coverage 89.56%), ruff, mypy, compileall all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Low-confidence web discovery for the sentiment-only evidence the deterministic pipeline cannot see: - backend/ipo/sources/enrichment.py: collect_enrichment_signals runs seven fixed query templates (GMP, news, promoter reputation, litigation red flags, anchor commentary, brokerage reviews, peer discovery) through the shared sixty_seven SerpApiClient and persists one ipo_enrichment_signals row per type. - Trust rules are structural: missing SERPAPI_API_KEY degrades to a graceful skip (the screener stays fully functional); every snippet is prompt-injection scanned BEFORE storage and a hit is replaced by the blocked-evidence marker with quarantined=True; red-flag evidence is recorded as matched keywords only (never snippet text); conservative GMP parsing requires an explicit GMP mention and stores NULL rather than guessing; confidence and source_policy are stamped on every row. - Per-type failure isolation: one failing query records its exception type and the rest of the batch still lands. - Domain repository record/list functions (SQL stays in storage), IpoEnrichmentSignalData DTO, EVENT_IPO_ENRICHMENT_* events, facade exports. Tests: tests/test_ipo_enrichment.py (no-key skip, injection quarantine round trip, GMP regex table incl. rupee->percent conversion and the no-price-band case, red-flag keyword capture, per-type isolation, typed not-found). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The parse stage IPO-003 deferred, split into two pure, AI-free modules: - backend/ipo/documents/table_extractor.py: extract_document_pages opens one hash-verified cached PDF (lazy pdfplumber import, injectable open_pdf seam) and returns 1-based ExtractedPage/ExtractedTable receipts - the provenance anchors later page citations are verified against. Hostile-content caps bound every dimension a PDF author controls (800 pages, 20k chars/page, 20 tables/page, 200 chars/cell); structural problems surface as typed IpoDocumentParseError codes (unreadable_pdf / page_limit_exceeded / empty_document) so parser tracebacks never leak file content. Oversized documents are rejected, not truncated, because truncation would invalidate page citations. - backend/ipo/documents/section_classifier.py: classify_pages assigns pages to DRHP/RHP section families via a reviewed anchor catalog (plain casefolded substring hits, argmax with catalog-order tie break); unmatched pages land in an explicit OTHER bucket and each receipt records exactly which anchors matched. Tests: fakes for the pdfplumber seam (caps, None cells, error codes) plus one true integration read through real pdfplumber against a byte-accurate minimal PDF assembled in-test (no binary fixture in the repo); classifier assignment/tie/OTHER/determinism table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The claude-agent-sdk extraction agent and the human review queue that keeps its output fail-closed: - backend/ipo/agents/financial_extractor.py: propose_extraction runs a locked-down SDK loop (permission_mode="dontAsk", setting_sources=[], in-process tools only - list_sections / read_section / read_tables) over the classified pages of one hash-verified cached PDF. Every excerpt the model sees is prompt-injection scanned first (a hit is non-retryable and fails the run); the final JSON is parsed against a strict pydantic schema (values as decimal strings, extra keys rejected) with one bounded retry via parse_with_retry; then the HOST independently verifies every citation - pages must exist and every number must literally appear on its cited page's text/tables. All verified -> high confidence; >=90% + all core values -> medium with reviewer notes; less -> fail closed, nothing persisted. Failures become typed IpoExtractionErrorReceipt values (batch style). - Review flow in the domain repository: submit_extraction_proposal (shape-validated before storage, one pending per document), approve_extraction_proposal (reconstructs the strict manual contract from the payload and replays submit_manual_extraction - the reviewer attests as entered_by_email and the cached PDF bytes are re-hashed), reject_extraction_proposal (attributable, reasoned, redacted). Audit events on every review decision. - ui/ipo_manual_page.py gains the "Review AI extraction proposals" section: pending queue, verifier notes, payload inspection, Approve / Reject controls wired to the signed-in admin identity. - EVENT_IPO_EXTRACTION_* observability events; facade exports. Tests: tests/test_ipo_financial_extractor.py (real pdfplumber pass over an in-test PDF, citation verification tiers, bounded retry, quarantine non-retry, duplicate/missing-value/parse-code receipts), tests/test_ipo_extraction_review.py (approve==manual revision round trip on a verified cache, double-review guards, reject audit trail), extended manual-page smoke tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The one-command screener and the shared scoring service both it and the dashboard call: - backend/ipo/scoring/service.py: rescore_issue loads the full evidence bundle (latest manual profile, on-demand ratios, newest subscription, enrichment signals), runs derive -> flags -> score -> verdict, and persists an immutable ipo-006-v1 evaluation ONLY when the SHA-256 inputs fingerprint changed. The fingerprint hashes evidence identities and rule versions plus two time-DERIVED facts (the set of GMP signals still inside the staleness window, and the near-close demand window) instead of the clock itself, so re-runs are idempotent until time actually changes a factor or flag. Issues without a verified profile report insufficient_inputs and write nothing - missing data never becomes a fabricated score. - backend/jobs/run_ipo_screener.py (python -m backend.jobs.run_ipo_screener): scan -> download -> enrich -> (--extract only) draft AI proposals -> score, mirroring the scan_ipo_filings template: full DI, per-unit failure isolation, [ipo-screener] key=value summary grammar (recommended / not_recommended with flags= / insufficient_data with missing= / totals), frozen outcome dataclasses with an exit_code contract. Missing SERPAPI_API_KEY is one graceful skipped_no_key line, never a failure; AI extraction stays behind --extract so schedulers and CI never spend model credit by accident. - IpoEvaluationRecord gains inputs_fingerprint; domain readers get_latest_evaluation / get_latest_subscription; screener events; facade + scoring-package exports (service deliberately NOT re-exported from backend.ipo.scoring to avoid an import cycle with the repository). Tests: tests/test_ipo_scoring_service.py (real file-backed DB round trip: evaluated -> skipped_unchanged -> price-band/subscription/GMP changes re-open the fingerprint; clock-independence of the fingerprint), tests/test_run_ipo_screener_job.py (stage gating, --extract targeting, isolation + exit codes, no-key skip, summary grammar, CLI wiring). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The read-only surface over everything the pipeline persists: - backend/ipo/dashboard.py (Streamlit-free): build_dashboard_snapshot denormalizes every issue's stored state - latest evaluation, manual profile, cached documents, pending proposals - into display-ready rows; pure section selectors implement the spec's seven sections (Available filings / Open / Upcoming / DRHP watchlist / Recommended / Not Recommended / Missing data queue); top_positive_and_risk_reasons ranks the stored contribution receipts against PDF_WEIGHTS (missing factors are never labeled risks - "could not check" and "checked and weak" stay distinct messages). IpoEvaluationRecord now restores the contributions receipt for this. - ui/ipo_page.py: _render_ipo_page(can_rescore, user_email) renders the sections, the binary verdict filter, and per-issue score-breakdown expanders (reasons, hard flags, missing data, source documents). The four stored recommendation_type strings map onto the sprint's friendly labels purely in the UI. The capability-gated "Re-score all issues" button runs the same repository-only scoring service the job uses, audits the outcome counts, and invalidates the 5-minute snapshot cache. Zero network inside render. - app.py: "IPO screener" joins the base view list for every authenticated user (same tier as Validation); the dispatch passes can_rescore=MANAGE_IPO_DATA. Orchestration tests updated (nav tuples, re-export identity, keyword-only capability boundary). Tests: tests/test_ipo_dashboard_builder.py (strength/risk selection, section membership, missing-data queue rules, snapshot denormalization over monkeypatched repositories), tests/test_app_ipo_page.py (label-map completeness against the 4 DB strings, filter semantics, spec column contract, render-touches-no-repository smoke, re-score audit + cache invalidation + failure isolation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ticket-scoped design docs for every sprint in this change, in the repo's decision-doc style: - ipo-006-factor-derivation-and-verdict.md: band tables, the None-vs-zero rule, the seven-flag catalog with its three outcomes, and the verdict precedence (missing-critical > triggered flags > score bands) behind the new 'Insufficient verified data' type. - ipo-007-dashboard.md: builder/renderer split, section rules, strengths/risks selection, and the capability-gated re-score action. - ipo-008-screener-orchestration.md: stage pipeline, failure and configuration semantics, summary grammar, and the inputs-fingerprint idempotency contract (time-derived facts, not the clock). - ipo-009-serpapi-enrichment.md: the structural trust rules for low-confidence web signals. - ipo-010-ai-extraction-proposals.md: the three trust tiers of automated extraction and the proposal/review model; deliberate deferrals (parse_status vocabulary, OCR). - components/ipo-extraction-ai.md: new LLD mirroring fundamentals-ai.md for the fourth Claude Agent SDK agent. - components/ipo-screener.md: status now IPO-001..010; new section mapping each addition to its design doc; extension points updated. - docs/architecture/README.md index entries; AGENTS.md repo-map row, design-doc index, and the run_ipo_screener command. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DoRmAmMu1997
left a comment
There was a problem hiding this comment.
Self-review: code review + security review (author-run, per AGENTS.md workflow)
Code review — verdict: no blocking findings
Verified beyond the gate suite (1640 tests, 89.9% coverage, ruff/mypy/compileall/bandit/pip-audit all green):
- Verdict precedence (missing-critical > triggered flags > score bands) and the binary invariant are pinned by tests, including a triggered flag overriding a 95-point score.
- Number-verification normalization exercised directly against edge inputs:
1,234.50with commas/₹/Rsnoise matches, parenthesised negatives(56.70)match-56.70, rounded variants match, absent numbers reject. Whitespace-stripping can merge adjacent digit runs, so a false acceptance is theoretically possible for a value that already appears on the page — the fail-closed direction is unaffected and human review remains the gate. - Import-cycle hazard between
scoring.serviceand the repository is resolved structurally (service deliberately not re-exported frombackend.ipo.scoring; comment documents why). - Fingerprint idempotency hashes time-derived facts (usable GMP set, near-close window) rather than the clock; clock-independence is pinned by a test.
- Known accepted nits: dashboard builder does per-issue reads (documented; universe is dozens of issues, page cached); the
entirely_ofs_weak_growthevidence line reads slightly oddly for a zero-fresh/zero-OFS issue (status is still correct); approve/approve race leaves one extra append-only revision and fails loudly (documented in the docstring and error message).
Security review — verdict: no critical/high findings; residual risks documented
- SSRF: no new arbitrary-URL fetches. Enrichment reuses the fixed-endpoint SerpAPI client (result links stored as data, never fetched); the extraction agent reads only the hash-verified local cache and never sees a path.
- Prompt injection (TEST-003): every prospectus excerpt handed to the model and every web snippet stored passes the shared quarantine engine first. Hits are non-retryable (extractor) or replace text with the blocked marker (
quarantined=truerows); raw hostile text never reaches storage, logs, or the model. - Trust boundaries are structural: AI output persists only as pending proposals scoring never reads; approval re-validates the payload through the strict manual contract and re-hashes the cached PDF bytes; enrichment signals are typed away from every financial-statement field.
- AuthZ: re-score requires
MANAGE_IPO_DATAat the dispatch boundary; proposal review lives inside the admin page with its defense-in-depth re-check; reviewer identity comes from the server session, never the form; review decisions and re-scores are audit-logged. - Secret safety: all new persisted JSON passes
normalize_secret_safe_json; rejection notes passredact_text; failure paths carry exception type names and stable codes only. - Resource exhaustion: hostile-PDF caps (800 pages / 20k chars / 20 tables / 200-char cells), bounded section chunks, bounded search results, linear regexes.
- Residual risks (accepted, consistent with the audit register's posture): (1) model-transcribed free text (objects of issue, peer names, verifier notes) is displayed to the reviewing admin — a hostile document could try to social-engineer the reviewer through it; the control is review against the source PDF plus host-verified citations. (2) The digit-merge false-acceptance noted above. Neither creates a path for unverified data to reach scoring.
🤖 Generated with Claude Code
DoRmAmMu1997
left a comment
There was a problem hiding this comment.
Codex review — changes required before merge
I reviewed the exact PR range de8f199…aab2cd4, including the IPO-006 through IPO-010 implementation, focused runtime reproductions, migration/layering policy, and the rendered dashboard path. The current CI is green, but the review found eight validated security/data-integrity issues:
- Medium — SerpAPI authority inversion: uncorroborated litigation snippet text can become a score-independent hard veto, including negated text such as “No litigation…”.
- Low — numeric evidence equivalence: rounded values, digit substrings, and values assembled across cells can pass host verification.
- Low — uncited unit scale: model-selected units are allowlisted but not bound to cited source evidence before
HIGH. - Low — row-wide quarantine fail-open: one hostile result quarantines clean siblings, after which the caution path reports
NOT_TRIGGERED. - Low — PDF page limit is post-materialization: the complete page tree is built before the page cap is checked.
- Low — PDF table limits are post-extraction/incomplete: all tables are extracted before slicing, with no row/column/total-cell budget.
- Low — PDF text limit is post-extraction: the parser constructs the complete text map/string before truncation.
- Low — debt-purpose negation: free-text substring matching treats negated repayment wording as affirmative evidence and can suppress the high-debt caution.
The broader correctness pass also found work required around proposal source freshness and atomic approval, database-enforced pending/evaluation uniqueness, reviewed-proposal retention, extraction rerun idempotency, semantic score fingerprints, consistent input snapshots, period ordering, page-safe chunks, continuation-page classification, GMP proximity parsing, dashboard provenance/freshness, factor-impact ordering, Markdown-safe rendering, and stale architecture/operations documentation.
The implementation will keep this already-combined IPO-006…010 PR, with an explicit waiver of the repository’s normal one-ticket-per-PR convention. Fixes will be test-first, preserve official/manual evidence precedence and human approval, and retain the existing deterministic scoring weights and verdict bands.
This is a COMMENT review rather than an approval/request-changes event because the automation identity is also the PR author.
Document the accepted PDF isolation, cited-evidence, enrichment authority, atomic review, and semantic idempotency boundaries before implementation. Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
DoRmAmMu1997
left a comment
There was a problem hiding this comment.
Codex post-security review — changes still required
The finalized branch-diff scan closed 24/24 coverage rows and validated seven remaining issues against local remediation head 7130cc3a606f0ddb9a2b3e30d5031a4df026737a (the six local remediation commits are not pushed yet):
- Financial evidence is token-bound but not yet semantically bound to the claimed field, period, and unit context.
- A page-wide unit fallback can select unrelated text instead of the cited value's own table/text context.
- Negated debt-repayment language can suppress the high-leverage caution.
- GMP parsing can consume an unrelated percentage from a neighboring clause.
- Semantically identical enrichment observations can be persisted repeatedly and overweighted.
- Untrusted Markdown control characters are not neutralized at every Streamlit rendering boundary.
- Provider-result object work is not bounded early enough before downstream processing.
These are fixable without changing the intended deterministic weights, recommendation bands, binary verdict, or advisory-only web-evidence policy. I will add failing regression tests first, apply the narrow authority/resource-boundary fixes, rerun the original local checks plus the repository gates, then push and update this review with evidence.
Repository convention waiver: PR #108 intentionally retains IPO-006 through IPO-010 as the already-combined delivery; this is an explicit exception to the normal one-ticket-per-PR rule.
Translate the approved PR #108 remediation design into five test-first, review-gated implementation tasks for the seven residual findings and hosted PR closure. Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
DoRmAmMu1997
left a comment
There was a problem hiding this comment.
Codex completion review — ready to merge
I re-reviewed the complete range
de8f199b937a2ec9c7f78d1ed86bf6924841fac4...326061d838dab57e6a17e0ad147582e0b88c44c8
after remediation.
The original eight validated findings and the seven residual post-review
findings are addressed with permanent regression coverage:
- official/manual evidence authority prevents SerpAPI hard-veto inversion;
- PDF work is killable and bounded before crossing the worker boundary;
- financial values, units, periods, field/peer meaning, pages, cells/spans,
source tokens, and document SHA are atomic v2 evidence; - cited narrative evidence and proposition-aware debt-purpose classification
fail closed on negation, ambiguity, conflict, legacy data, and forgery; - proposal approval, retention, uniqueness, force-rerun, and evaluation
idempotency are transactionally and semantically enforced; - per-item quarantine, same-clause GMP, semantic deduplication, advisory
authority, and streamed provider limits prevent web-evidence escalation; - seven-row score provenance, source freshness/staleness, deterministic impact
ordering, and Markdown-safe Streamlit rendering are public and tested.
The first hosted run exposed a Linux-only spawn bootstrap problem: importing
the broad backend.ipo facade consumed address space before the 512 MiB child
limit. Commit 326061d moves the target to dependency-light
backend/ipo_pdf_worker.py, preserving the limit and allowing pdfplumber to
start. The rerun is fully green:
- Python 3.11 quality and supply-chain checks: passed.
- Python 3.12 quality and supply-chain checks: passed.
- Docker image build: passed.
- Focused IPO suite: 464 passed, 1 skipped.
- Exact cross-finding selection: 68 passed.
- Migration parity: 13 passed.
- Whole-tree mypy: 245 source files, no issues.
- Ruff, compileall, Bandit, pre-commit config, and pip-audit: passed.
The PR description now includes the explicit IPO-006…010 combined-ticket
waiver, both migration notes, security closure, residual Windows memory-limit
risk, and verification evidence. The live PR is MERGEABLE / CLEAN, all
checks pass, and there are no inline review threads.
This is a COMMENT review because the automation identity is also the PR
author and cannot self-approve.
Add beginner-friendly docstrings and inline comments throughout the IPO-006 through IPO-010 implementation. Explain the safety boundaries, evidence authority, transaction ownership, scoring semantics, and UI rendering rules without changing runtime behavior. Co-authored-by: Codex <codex@openai.com>
|
Documentation pass completed on final head
No review threads remain unresolved, and the PR is mergeable. |
Expand the repository, scorecard, and verdict test module notes so beginners can understand the persistence, fail-closed scoring, and public JSON contracts that the scenarios protect. Co-authored-by: Codex <codex@openai.com>
Summary
Delivers the already-combined IPO-006 through IPO-010 scope: deterministic
factor scoring and binary verdicts, the read-only dashboard, idempotent
orchestration, advisory SerpAPI enrichment, and contained AI-assisted PDF
extraction proposals behind human review.
Repository-convention waiver: this PR intentionally keeps IPO-006 through
IPO-010 together because the implementation was already delivered as one
dependent branch before remediation. This is an explicit exception to the
normal one-ticket-per-PR rule.
The central invariant is unchanged: only approved official/manual evidence
can enter scoring. AI output is a pending proposal, and web results are
quarantined advisory observations.
Post-review hardening
row, column, cell, glyph, text, serialization, and Linux address-space
budgets. The dependency-light
backend/ipo_pdf_worker.pytarget applies the512 MiB Linux limit before importing pdfplumber.
field/peer label, fiscal period header, cited unit, document SHA, page, and
table cell/text span. Exact cited narrative evidence is required for objects
of issue; submission and approval both re-resolve receipts from verified
cached bytes.
pending proposal and semantic proposal/evaluation uniqueness in the
database, retains reviewed proposal history, and adds
--force-extractwithout allowing duplicate pending or identical proposals.
negation-aware red-flag observations, same-field/same-clause GMP parsing,
canonical semantic deduplication, and streamed 1 MiB/2,000-character provider
bounds. Web evidence cannot independently create a hard veto.
contribution rows whose sum is the score, closes concurrent evaluation
races, and exposes source/freshness/staleness on the dashboard.
conflicting, ambiguous, missing, or legacy text cannot clear the high-debt
caution.
label/caption/warning/error/expander sink without enabling unsafe HTML.
Documentation pass
inline rationale in 24 Python files. The remaining touched modules and
tests already had explanatory module notes or explicit regression-contract
documentation, so they were reviewed without unnecessary churn.
transaction/session ownership, semantic idempotency, deterministic scoring,
migration safety, test intent, and no-network/no-unsafe-Markdown UI
boundaries next to the code that enforces them.
one-pending-proposal invariant.
commits change documentation only; they do not alter executable Python
behavior.
Migrations
20260713ipo006_screener_artifacts.pyintroduces the IPO-006 evaluation,proposal, and enrichment artifacts.
20260718ipo010_hardening.pyadds versioned evidence/breakdown fields,pending-proposal and semantic-fingerprint uniqueness, non-null evaluation
fingerprint uniqueness, retention-safe reviewed proposal references, and
enrichment semantic first/last-seen identity.
Base.metadata; policyfixtures and hardcoded table/index expectations are updated.
Security findings addressed
The original review's eight findings and the finalized scan's seven residual
findings are covered by permanent regressions: official/manual authority over
SerpAPI, exact numeric/unit/period/field binding, per-item quarantine,
pre-boundary PDF resource containment, negated debt purpose, same-clause GMP,
semantic enrichment deduplication, provider response bounds, and Markdown-safe
rendering.
The accepted residual risk remains documented: Windows has no standard-library
hard RSS limit, so it uses wall/object/text/result containment; Linux enforces
the 512 MiB child address-space limit.
Verification
(required: 87%).
pip-audit reports no known vulnerabilities.
30202559559
on final head
594583a:Co-authored by Claude Fable and Codex.