# Post-answer Non-L1 Reconciliation

> The terminal-job candidate scope and Base funnel semantics are revised by
> `2026-07-17-l1-funnel-and-non-l1-exclusion.md`.

## Current Design

- The Jira poller stores normalized ticket snapshots and enqueues one initial
  job. The worker fetches the current Jira issue, evaluates the L1 Oncaller
  Gate, then may run analysis and write one DB-mapped Jira internal comment.
- Newer workers record a ticket that is L1-owned when the worker runs in
  `auto_oncall_l1_population`, but the currently deployed schema predates that
  table. A later Jira reassignment does not currently revisit the decision.
- The latest initial job and gate state in MySQL are the canonical automation
  outcome. Feishu Base is an idempotent downstream projection of ticket, job,
  gate and run state.
- The initial Jira internal comment target is canonically mapped by
  `auto-live:v1:<issueKey>:initial`; the update path performs PUT by that stored
  comment ID and must not scan Jira bodies or create a replacement comment.
- The L1 Oncaller Gate previously authorized only the `Intelligent Support`
  child department through `AUTO_ONCALL_L1_ONCALLER_ALLOWED_DEPARTMENT_IDS`.
  Escalation to sibling Support teams such as `Advanced Intelligent Support`
  is still inside Support ownership. The runtime Feishu app now has data
  authority over their common parent, `Global Support Intelligence`.

## Risks

- Jira, MySQL and Feishu Base do not share a transaction. Updating DB before
  Jira would remove the ticket from the retry candidate set while leaving a
  stale automatic answer visible.
- Re-evaluating tickets without a strict creation window could rewrite old
  cases or reinterpret historical ownership.
- Treating `oncaller_missing` or a directory/API failure as confirmed non-L1
  could invalidate a valid answer on ambiguous evidence.
- Mutating historical run rows would destroy the audit of what actually ran.
- A Base write may fail after the Jira and DB correction; Base therefore needs
  an immediate best-effort refresh plus its existing periodic repair path.
- Choosing a child department would continue to misclassify sibling Support
  teams. Choosing a broader ancestor than `Global Support Intelligence` would
  admit unrelated organizations.

## Target Contract

```text
initial L1-eligible job succeeds and writes internal comment
  -> periodic rolling scan while Jira created age is 0..60 minutes
  -> fetch current Jira ticket and persist a new normalized snapshot
  -> re-run the existing L1 Oncaller Gate
  -> only on confirmed oncaller_not_confirmed_l1:
       PUT the mapped initial internal comment with a short invalidation note
       -> transactionally mark initial job skipped, replace latest L1 gate
          state, and remove it from canonical L1 population
       -> immediately re-project that Ticket Key to Feishu Base
```

- MySQL latest job/gate/population state remains canonical. Base never writes
  automation status back to MySQL.
- The scan only considers succeeded initial jobs with a canonical latest ticket
  snapshot, a DB-mapped initial internal comment and Jira creation time within
  the configured 60-minute rolling window. It does not require the newer L1
  population table, so in-window jobs created by the preceding deployed schema
  are not missed.
- Repeated scans are idempotent: once corrected, the job is no longer a
  succeeded candidate; Jira updates always target the same stored comment ID;
  Base upserts by Ticket Key.
- The update order is Jira PUT, then one DB transaction, then Base. A Jira
  failure leaves the DB candidate retryable. A Base failure leaves canonical DB
  truth corrected and is repairable by normal periodic Base sync.
- The gate scope means Support ownership, not literal L1-only ownership.
  `AUTO_ONCALL_L1_ONCALLER_ALLOWED_DEPARTMENT_IDS` contains only the
  `Global Support Intelligence` parent department
  (`od-09c379b155eaa1c7f9e996c284069e11`); the existing recursive directory
  lookup admits members of its Support child departments.
- No team-member email allowlist is used for this scope. Organization changes
  are reflected through Feishu department membership.

## Invariants

- `oncaller_not_confirmed_l1` is the only correction decision. Missing
  oncaller data and directory/API failures do not invalidate an answer.
- Escalation from L1 to `Advanced Intelligent Support` must remain allowed and
  must never trigger post-answer invalidation.
- The scanner never runs Codex, creates a workspace, writes a customer-visible
  comment, creates a Jira comment, transitions an issue, or performs a
  production operation.
- Existing `auto_oncall_runs` rows and their Jira delivery JSON remain immutable
  audit records.
- No Jira-body, run-JSON, Base, or historical-workspace fallback is introduced.
- Base failures never roll back Jira or DB correction and never fail a worker.
- Production automation and production Base synchronization remain disabled.

## Implementation Steps

1. Configure the `Global Support Intelligence` parent department in the
   staging and disabled-prod source templates, and lock recursive department
   admission with a local gate contract test.
2. Add staging-first rollout configuration for an explicit reconciliation
   enable flag, 60-minute window and periodic interval; keep prod disabled.
3. Add a bounded DB candidate query and one transactional correction method.
4. Add a narrow Jira comment renderer/updater that only accepts a stored
   initial comment ID.
5. Add targeted one-ticket Base projection using the existing analytics and
   publisher contracts.
6. Add the guarded periodic runtime and start/stop it with the service.
7. Document configuration and behavior.

## Verification Checklist

- A succeeded initial job reassigned from L1 to confirmed non-L1 inside the
  first hour updates the same Jira comment ID, becomes
  `skipped/oncaller_not_confirmed_l1`, leaves L1 population, and publishes an
  out-of-scope Base row.
- A still-L1 ticket is unchanged.
- Members of `Intelligent Support`, `Advanced Intelligent Support`, and
  `Intelligent Support Enablement` are admitted through the recursive parent
  department lookup.
- A missing oncaller, directory failure, missing comment mapping, Jira PUT
  failure, DB failure and Base failure preserve the documented retry/repair
  boundaries.
- Tickets older than one hour, non-succeeded jobs and continuation jobs are not
  candidates.
- Repeated scans do not create Jira comments, duplicate DB records or duplicate
  Base rows.
- Snapshot persistence includes the current Jira update before correction.
- `npm run typecheck`, focused tests, full `npm test`, and `git diff --check`
  pass before any PR or deployment.
- Merge the source-template app-delivery PR, deploy staging, then verify live
  pod env, scan logs, exact Jira comment ID, DB job/gate/population state, and
  the Base Ticket Key row.
- After the new gate is live, restore every previously corrected ticket that
  the parent-department gate now allows, then re-run the 14-day audit.

## Non-Goals

- No reconciliation after the configured one-hour creation window.
- No change to continuation invocation semantics.
- No customer-visible reply deletion or modification.
- No mutation of historical run rows or report artifacts.
- No email allowlist for Support organization membership.
- No prod cutover or production mutation; prod remains disabled even though its
  source template carries the same gate scope for a future cutover.
