Skip to content

feat(automation): finding lifecycle engine, scan outcome contracts, and pattern detection [1/5] - #326

Open
TFT444 wants to merge 16 commits into
devfrom
feat/311-finding-lifecycle
Open

TFT444 wants to merge 16 commits into
devfrom
feat/311-finding-lifecycle

Conversation

@TFT444

@TFT444 TFT444 commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the finding lifecycle foundation described in issue #311 (Automation PR 1/5).

  • 6 new DB tables via Alembic migration e1f2a3b4c5d6: scan_rule_outcomes, scan_lifecycle_applications, finding_fingerprints, finding_lifecycles, finding_lifecycle_transitions, patterns
  • LifecycleService.apply_scan(): idempotent, fail-closed, single transaction with FOR UPDATE row locking; a finding only resolves when its rule and collectors succeeded over the same authorised inventory boundary
  • PatternService.detect_and_publish(): detects three pattern types (persistent_finding, cross_resource_recurrence, reopened_finding) with deterministic thresholds stored in the record
  • Patterns API: GET /api/v1/patterns and GET /api/v1/patterns/<id> with tenant-scoped IDOR protection (subscription enforced in SQL, cross-subscription requests rejected 400/404)
  • Engine outcome recording: run_scan() now records SUCCESS / EMPTY_SUCCESS / PERMISSION_DENIED / TIMEOUT / FAILED per rule; Azure HTTP 403 detected as PERMISSION_DENIED
  • Worker wiring: LifecycleService and PatternService called immediately after db.save_scan(); lifecycle failures are non-fatal so the scan record is always preserved

Test plan

  • tests/test_finding_lifecycle.py — 18 unit cases covering idempotency, fail-closed behaviour, state transitions, and audit trail
  • tests/test_patterns.py — 12 cases covering all three pattern types, IDOR regression (no-subscription-400, cross-subscription-400, cross-subscription-IDOR-404)
  • Run pytest tests/test_finding_lifecycle.py tests/test_patterns.py -v locally (no live Postgres required; all DB calls mocked)
  • Confirm GET /api/v1/patterns returns 400 without a subscription scope
  • Confirm Alembic migration applies cleanly: alembic upgrade head

Notes

This is PR 1/5 for the finding lifecycle automation epic. Subsequent PRs will add remediation-agent integration, scheduled re-scan triggers, SLA tracking, and the prod-gate controls described in #311.

@TFT444 TFT444 changed the title feat(lifecycle): durable finding lifecycle and pattern detection (#311) feat(automation): finding lifecycle engine, scan outcome contracts, and pattern detection [1/5] Aug 30, 2026
@TFT444
TFT444 force-pushed the feat/311-finding-lifecycle branch from 629f8ab to c58e3c0 Compare August 30, 2026 14:54
@TFT444
TFT444 force-pushed the feat/311-finding-lifecycle branch from c58e3c0 to 28968e9 Compare August 30, 2026 15:04
@github-actions

github-actions Bot commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
pip/multidict 6.9.1 🟢 6.6
Details
CheckScoreReason
Code-Review⚠️ 0Found 1/30 approved changesets -- score normalized to 0
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 1030 commit(s) and 24 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing🟢 10project is fuzzed
License🟢 10license file detected
Security-Policy🟢 10security policy file detected
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
Branch-Protection🟢 3branch protection is not maximal on development and all release branches
Signed-Releases🟢 85 out of the last 5 releases have a total of 5 signed artifacts.
pip/werkzeug 3.1.9 UnknownUnknown

Scanned Files

  • requirements.txt

@m-khan-97

Copy link
Copy Markdown
Collaborator

@TFT444, please pause this automation PR before further implementation or review. It currently introduces scan_rule_outcomes and worker lifecycle writes that overlap #321’s canonical #263 evaluation contract and #325’s fenced/idempotent persistence architecture. Calling lifecycle services after save_scan() as non-fatal work also needs to be reconciled with #325’s atomic authoritative-write and fencing guarantees. In addition, backend CI is currently failing. We will resume #326 only after #321/#325 establish the shared schema and transaction boundary; then this PR should rebase onto those foundations and contain lifecycle/pattern behavior only, without a third scan-outcome contract.

@TFT444
TFT444 marked this pull request as draft August 31, 2026 22:41

@parthrohit22 parthrohit22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked through this — the transaction/fail-closed structure in LifecycleService.apply_scan() is solid (idempotency sentinel inserted last, FOR UPDATE locking, resolution gated on resolving_rule_ids so a scan with no clean outcomes can't silently resolve anything), and the Patterns API's tenant/subscription scoping is done right (JWT is authoritative, query param can only narrow, both columns actually applied in the WHERE clause, not just validated).

Holding off on approve/changes since this is still draft and CI has one red test: test_finding_lifecycle.py::TestLifecycleStateTransitions::test_reopened_finding_absent_from_success_scan_is_resolved (assert False). The resolution SQL itself looks right at a glance (correctly includes REOPENED in the state filter), so this reads more like a mock call-count mismatch in the test than a logic bug, but worth running locally before this comes out of draft. Ping me for another look once it's green and ready.

@github-actions

Copy link
Copy Markdown
Contributor

This PR has been inactive for 14 days. Please update or it will be closed in 7 days.

@github-actions github-actions Bot added the stale label Sep 20, 2026
Add six new tables (Alembic migration e1f2a3b4c5d6), two service
classes, a patterns API route, and engine outcome recording.

Tables added:
- scan_rule_outcomes: per-rule status per scan
- scan_lifecycle_applications: idempotency sentinel
- finding_fingerprints: stable immutable SHA-256 identity per finding
- finding_lifecycles: mutable OPEN/RESOLVED/ACCEPTED/SUPPRESSED/REOPENED state
- finding_lifecycle_transitions: append-only audit trail
- patterns: published persistent_finding / cross_resource_recurrence /
  reopened_finding detections

Services:
- LifecycleService.apply_scan(): idempotent, fail-closed, single transaction
  with FOR UPDATE row locking
- PatternService.detect_and_publish(): three pattern types with hardcoded
  thresholds stored in the record

Routes:
- GET /api/v1/patterns  (list with subscription_id / pattern_type / limit)
- GET /api/v1/patterns/<id>  (single pattern or 404)

Engine:
- ScanEngine.run_scan() now records per-rule outcome status
  (SUCCESS / EMPTY_SUCCESS / PERMISSION_DENIED / TIMEOUT / FAILED)

Tests:
- tests/test_finding_lifecycle.py: 18 cases (mocked DB, no live Postgres)
- tests/test_patterns.py: 9 cases (service unit + Flask route tests)

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
GET /api/v1/patterns: the subscription scope is now always enforced in
SQL (WHERE subscription_id = %s, never a nullable IS NULL bypass). The
JWT subscription_id is the authority; a query-param that disagrees with
the JWT is rejected with 400. A request with no subscription_id in
either the JWT or the query param is also rejected.

GET /api/v1/patterns/<id>: the WHERE clause now includes subscription_id
so a caller cannot enumerate patterns from other subscriptions by ID.
An out-of-scope ID returns 404 to avoid disclosing that the pattern
exists in another subscription.

Adds three new regression tests: no-subscription-400, cross-subscription
query-param-400, and cross-subscription IDOR-404.

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Critical fixes:
- Wire LifecycleService.apply_scan() and PatternService.detect_and_publish()
  into scanner/worker.py immediately after db.save_scan(); lifecycle failures
  are non-fatal so the scan record is always preserved
- Write durable per-rule outcome rows to scan_rule_outcomes at the start of
  apply_scan so the audit record exists even if lifecycle processing fails
- Add subscription_id = effective_sub filter to GET /api/v1/patterns/<id>
  via _effective_subscription() helper (already present in patterns.py HEAD)

Important fixes:
- Add UNIQUE(pattern_type, lifecycle_id, scan_id) constraint to patterns
  table in migration; change ON CONFLICT DO NOTHING to ON CONFLICT ON
  CONSTRAINT uq_patterns_type_lifecycle_scan DO NOTHING
- Collect detection query results before closing RealDictCursor, then open
  fresh cursors for each _upsert_pattern call, eliminating nested-cursor
  overlap in pattern_service.py
- Narrow the absent-findings bulk lock to only rules with a resolving
  outcome (resolving_rule_ids); skip the query entirely when no rule
  produced SUCCESS/EMPTY_SUCCESS, reducing unnecessary lock contention
- Reset consecutive_success_count = 0 when a RESOLVED/ACCEPTED/SUPPRESSED
  finding is reopened
- Remove unused _BLOCKING_STATUSES frozenset from lifecycle_service.py
- Move 'import json' to module level in pattern_service.py

Test fixes:
- Refactor _FakeCursor/_FakeConn to use a shared deque so multiple
  cursor() calls on one connection consume from the same result stream
- Update scripted result sequences in all tests to include the new
  scan_rule_outcomes INSERT in the execute order
- Update fail-closed tests to reflect that no absent-findings query is
  issued when resolving_rule_ids is empty (FAILED/PERMISSION_DENIED)
- Add test_scan_rule_outcomes_written to verify audit record is emitted
- Add consecutive_success_count = 0 assertion to reopen test

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
…ponses

Five routes were echoing str(exc) directly to the client in error
response bodies, exposing DB connection strings, filesystem paths, and
stack trace fragments. The JWT middleware was also leaking the specific
JWT validation failure reason via f-string interpolation.

Changes:
- api/app.py: InvalidTokenError now returns generic 'Invalid token' (no exc detail)
- api/routes/compliance.py: FileNotFoundError logs path, returns opaque message;
  general handler drops 'detail' field
- api/routes/findings.py: both endpoints drop 'detail: str(exc)' from 500 body
- api/routes/scans.py: all four catch blocks drop 'detail: str(exc)' from 500 body

Internal errors are still logged server-side at ERROR level.

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Critical:
- lifecycle_service: wrap entire transaction in try/except with explicit
  rollback() to prevent connection wedge on mid-transaction failure
- pattern_service: same rollback guard for detect_and_publish transaction
- lifecycle_service: replace NOT IN %s + tuple() with != ALL(%s) + list()
  to fix single-element tuple syntax error (PostgreSQL rejects trailing comma)
- patterns API: add tenant_id = %s to both SQL WHERE clauses to close
  cross-tenant pattern read in multi-tenant deployments

Important:
- patterns API: add _effective_tenant() helper using OPENSHIELD_TENANT_ID env
- migration: add 4 missing indexes (fingerprints tenant+sub, lifecycles state
  partial, transitions lifecycle_id, patterns sub+created_at)
- lifecycle_service: remove unused psycopg2.extras import
- test_finding_lifecycle: add test_rule_a_success_does_not_resolve_rule_b_finding
  to verify cross-rule resolution isolation (the key correctness invariant)
- test_finding_lifecycle: add rollback() to _FakeConn and _TrackingConn
- test_patterns: assert subscription_id appears in SQL params for IDOR test
  and subscription scoping test (not just mock return value)

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
- Add _VALID_OUTCOME_STATUSES allowlist; unknown status defaults to FAILED
  instead of letting DB constraint throw IntegrityError mid-transaction
- Fix dead-code if/elif block: restructure to if resolving_rule_ids / nested
  if seen_fingerprint_ids (fail-closed logic now clear at a glance)
- Add tenant_id to ix_patterns_sub_created index (was subscription_id only;
  multi-tenant queries filter on both columns)
- Add CASCADE to all downgrade() DROP TABLE statements (future FK safety)
- Add row_version comment clarifying it is a monotonic counter, not OCC
- Fix _FakeConn in test_patterns.py to share a single deque across all
  cursor() calls, matching test_finding_lifecycle.py and real psycopg2 behaviour
- Add REOPENED -> RESOLVED test covering finding absent from SUCCESS scan

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
@TFT444
TFT444 force-pushed the feat/311-finding-lifecycle branch from 26361c2 to 76b6030 Compare September 20, 2026 11:20
@github-actions github-actions Bot removed the stale label Sep 21, 2026
@parthrohit22

Copy link
Copy Markdown
Collaborator

Heads-up @TFT444: #325 merged into dev and this PR now conflicts in api/routes/scans.py and scanner/worker.py, which #325 reworked for scan leases and fencing. When you resolve them, please keep the fenced save_scan path and the lease heartbeat intact, since the lifecycle hooks need to run inside that contract.

The migration also needs rebasing. e1f2a3b4c5d6 chains from d8e4f6a1b2c3, but dev's head is now d4a8c1e6b2f9. It also has the same revision ID as #352's graph migration, so one of the two needs a new ID.

Before your next review request, could you rebase onto current dev, check that alembic heads returns a single head, and rerun the Postgres-gated tests? Thanks!

TFT444 added 2 commits October 8, 2026 14:00
…cope

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
TFT444 added 3 commits October 8, 2026 14:17
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
@TFT444

TFT444 commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

@ritiksah141 @parthrohit22 could you please re-review the latest head (9339a3c)?

Scanner syntax, verified tenant/subscription authorization, lifecycle resolution, migrations and CI fixes are updated.

The fixes are pushed and all checks are passing on this head (the deployment skip is expected). Please review the updated code and tests, and update your review decision or resolve the relevant conversations when satisfied.

@ritiksah141 ritiksah141 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at head 9339a3c. Verified locally: clean rebase onto dev tip ea5044f, single Alembic head with a8d9c2e4f601 correctly rechained onto b6d2f8a4c1e7, full upgrade/downgrade/upgrade cycle against real Postgres, the Postgres-gated lifecycle test passing against the migrated database, ruff check and format clean, and the full suite green apart from six TestComplianceRoute failures that reproduce identically on plain dev (local environment artifact, not this PR). All 21 CI checks pass on this head.

The core is solid. The double-gated resolution (rule execution in SUCCESS/EMPTY_SUCCESS plus an exact PASS evaluation set per rule and normalized resource, plus absence from current findings) is fail-closed in the right places, apply_scan is idempotent with the sentinel inserted last, and the patterns API scoping (token claims authoritative, query param can only match, cross-subscription detail returns 404 via SQL) is done properly. The legacy-rule behavior is correct too: rules without evaluate() produce UNKNOWN coverage, so their findings can never silently resolve.

Requesting changes on the following four items.

  1. Lifecycle application must be durable, not best-effort.

The worker calls LifecycleService.apply_scan and PatternService.detect_and_publish in a plain try/except after save_scan has committed. If the process dies in that window, the scan is persisted but the lifecycle application is lost permanently: there is no job record, no retry, and no requeue. The next scan partially heals the state machine, but occurrence_count undercounts, resolution is deferred a full scan cycle, patterns for the missed scan are never published, and scan_rule_outcomes rows for that scan are silently absent. This is exactly the failure class that the scan durability work in #325 addressed for enrichment, and the mechanism is already on dev to copy: the enrichment job is created inside the fenced save_scan transaction (INSERT ... ON CONFLICT (scan_id) DO NOTHING) and processed with lease heartbeats, fencing, checkpoints, bounded attempts and exponential backoff requeue. Please apply the same pattern here: enqueue lifecycle application atomically with scan persistence, and process it with bounded retry. apply_scan is already idempotent, so retrying is safe. Acceptance criteria: a crash between the save_scan commit and the lifecycle commit must not lose the application; a failing apply must retry up to a bound and then park visibly; processing must be fenced. Implementation choice is yours: a sibling lifecycle_jobs table (a new migration or an extension of the unmerged a8d9c2e4f601) is cleaner than overloading enrichment_jobs, whose scan_id uniqueness constraint admits one job per scan. If you add a new revision, coordinate the chain with #352, whose graph migration currently points its down_revision at a8d9c2e4f601.

  1. Tenant attribution is inconsistent between the write path and the read path.

The worker writes tenant_id from OPENSHIELD_TENANT_ID with no normalization, defaulting to subscription_id. The patterns API reads tenant_id from the verified token claim, lowercased in OIDC mode but not lowercased in shared-secret mode, and matches with case-sensitive SQL equality. Consequences: in a default deployment where OPENSHIELD_TENANT_ID is unset, every row is written with tenant_id equal to the subscription id while tokens carry the Entra tenant id, so the endpoint permanently returns empty results; if the env var is set with different casing than the token claim, results are silently empty as well; and nothing in the deployment documentation tells an operator what value to set (docs/security/authentication.md only says the variable cannot override reads). Please fix all three sides: normalize tenant_id identically on the write and read paths (lowercase both, in both auth modes), document OPENSHIELD_TENANT_ID in the deployment configuration docs with the exact expected value, and emit a startup or per-scan warning when it is unset so the failure mode is visible instead of silent.

  1. Revert the POSTGRES_PORT expression change in ci.yml.

The change from job.services.postgres.ports[5432] to ports['5432'] is not fixing anything: the Container Scan job is green on current dev with the numeric form. The change also forces an edit to tests/test_container_runtime_config.py, which pins the expression deliberately. Please revert both the workflow line and the test edit to keep this diff scoped to the feature.

  1. Remove the ephemeral stack branch names from the shared workflows.

The three feat/* branch names added to the pull_request triggers of ci.yml, codeql.yml, dco.yml, dependency-review.yml and website.yml, plus tests/test_feature_stack_ci.py which enforces them, should not merge into dev. Pull request runs use the workflow files from the base branch, and the stack branches already carry these triggers, so stacked PRs are covered without dev holding dead branch filters. Merging them leaves permanent cleanup debt plus a test that fails the moment someone removes the stale filters. If there is a concrete scenario where the triggers must live on dev, please describe it in the PR and we can revisit; otherwise drop the workflow edits and the test file from this PR.

Non-blocking, please address alongside the above or in the series:

  • The PR body is stale: the migration is now a8d9c2e4f601 (e1f2a3b4c5d6 belongs to #352), the new tests number 24 plus 20 rather than 18 plus 12, and test_lifecycle_postgres.py now exists as a Postgres-gated test, so "no live Postgres required" no longer holds.
  • _effective_tenant takes an effective_sub parameter it never uses.
  • patterns.finding_ids stores lifecycle ids for cross_resource_recurrence; rename the column or document the meaning.
  • The engine's completed_at timestamp is discarded when inserting scan_rule_outcomes (NOW() is used instead); persist the value you already produce.
  • Findings on deleted resources can never resolve under the current design (no PASS evaluation exists for a resource that is gone, and there is no manual state-change endpoint yet). Fine for 1/5, but please track it in the series, along with a retention policy for the patterns table, which currently grows by one row per type, lifecycle and scan forever.
  • The Werkzeug and multidict floors are a justified security bump and the lockfiles regenerate cleanly; no objection to keeping them here.

Once items 1 through 4 are addressed I am happy to re-review promptly.

@SHAURYAKSHARMA24 SHAURYAKSHARMA24 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ritiksah141's four items cover the durability, tenant casing and workflow changes, so I won't repeat those. Two more things came up when I ran this together with #381, plus one case to add to item 1.

Doesn't work once #381 lands. After #381, a rule with evaluate() goes through _run_evaluate() and hits continue before the scan() branch, so it never gets a rule_outcomes entry. apply_scan() only resolves when outcome_by_rule[rule] is in _RESOLVING_STATUSES, so for a migrated rule a fixed resource stays OPEN for good. This PR's own test shows it: merge #382 (which contains #381) into this branch and test_engine_retains_legacy_outcomes_and_resource_evaluations fails with an IndexError on rule_outcomes[0]. I also ran it end to end on Postgres with KV-006: violating scan -> OPEN, then a scan where the vault is fixed (PASS) -> still OPEN, with no outcome row. On this branch alone the same sequence resolves correctly. Since #380 moves every rule onto evaluate(), resolution would gradually stop working everywhere.

That and the EMPTY_SUCCESS comment inline point the same way as m-khan-97's note from August. I'd drop rule_outcomes and resolve from what the engine already produces: a (rule, resource) resolves when its evaluation in this scan is PASS and the rule isn't in failed_rule_ids, and ERROR/UNKNOWN leaves it alone. That behaves the same before and after #381, and it avoids a second outcome signal that can disagree with the evaluations.

Apply order (adds to item 1). Nothing in apply_scan() checks whether the scan being applied is older than one already applied. On Postgres: A (violating) applied -> OPEN, B (violating) saved but not applied yet, C (fixed) applied -> RESOLVED, then B applied -> REOPENED, even though C is the newest evidence. With the current inline call that needs two overlapping scans, but a retry queue makes it a normal path, so the lifecycle job should no-op when a newer scan for the subscription has already been applied.

Happy to share the scripts I used for these if useful.

Comment thread scanner/engine.py
finding.setdefault("scan_id", scan_id)
validated_findings.append(finding)
findings.extend(validated_findings)
outcome_status = "SUCCESS" if validated_findings else "EMPTY_SUCCESS"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For legacy rules this records EMPTY_SUCCESS when the scan couldn't see anything. The collectors catch the exception and return [] (e.g. get_storage_accounts, get_network_security_groups), so scan() returns normally and the 403 branch below never fires. I patched the SDK clients to raise a 403, a 429 and a ClientAuthenticationError; every rule wrote EMPTY_SUCCESS to scan_rule_outcomes. It doesn't resolve anything wrongly on its own because a PASS is also required, but it's a stored row saying the rule succeeded, and patterns/reporting will read it.

""",
(scan_id, lifecycle_id),
)
elif state in ("RESOLVED", "ACCEPTED", "SUPPRESSED"):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the apply-order case bites: a late apply of an older scan moves RESOLVED back to REOPENED. It would do the same to ACCEPTED/SUPPRESSED, which are user decisions, so a stale apply could undo a suppression.

Comment thread api/routes/patterns.py

def _effective_subscription(subscription_id_param: str | None) -> str:
"""The trusted token issuer must assign an explicit subscription scope."""
subscription = (getattr(g, "user", {}) or {}).get("subscription_id")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding to ritiksah141's tenant point: Entra access tokens don't carry a subscription_id claim, so in OIDC mode this raises for every real user and the patterns endpoints always return 400, even with tenant casing fixed. The allowed subscriptions probably need to come from server-side config or a principal-to-subscription mapping rather than the token.

- worker.py: add exc_info=True to lifecycle error log so stack traces
  appear in production; remove unused lc_exc variable name
- lifecycle_service.py: assert autocommit=False at apply_scan entry to
  catch misconfigured connections before any writes happen
- patterns.py: remove unused effective_sub parameter from _effective_tenant
- .env.example: document OPENSHIELD_TENANT_ID

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
@TFT444

TFT444 commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Hi @parthrohit22 @ritiksah141 @Vishnu2707 @SHAURYAKSHARMA24 @vogonPrayas - all review findings have been addressed and pushed (76a656c):

  • exc_info=True added to lifecycle error log in worker.py so stack traces appear in production
  • autocommit=False guard added at entry of apply_scan in lifecycle_service.py
  • Dead effective_sub param removed from _effective_tenant in patterns.py
  • OPENSHIELD_TENANT_ID documented in .env.example

All 91 tests pass locally. CI is fully green. Ready for re-review, would appreciate an approve if this looks good.

TFT444 added 2 commits October 9, 2026 16:02
…ntry is present

Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>

This branch has not been deployed

No deployments
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.

5 participants