Skip to content

jobs(#864): retention holds untriaged Missing rows past 90d + dlq_untriaged_missing alert - #1336

Merged
radandevist merged 3 commits into
developfrom
lane/wt-864
Aug 25, 2026
Merged

jobs(#864): retention holds untriaged Missing rows past 90d + dlq_untriaged_missing alert#1336
radandevist merged 3 commits into
developfrom
lane/wt-864

Conversation

@radandevist

Copy link
Copy Markdown
Collaborator

Closes #864

Implementer: Ox Alpha (stealth/ox-alpha via Nous Portal, max effort, jcode). Reviewer: pending adversarial review.

Problem (K-2, #852 design §11)

job-dead-letter-retention hard-deletes any job_dead_letter row older than JOB_DEAD_LETTER_RETENTION_DAYS (90 default). A future 4 Missing row records an integrity anomaly (prepared state that should exist does not); deleting it at the age floor silently clears its alert with nobody having looked.

Owner decision on #864 (Radan, 2026-08-01): acknowledgement is required before a Missing row becomes retention-eligible; ship the flag + exemption now; the operator surface arrives with #636; and an alert must watch the unacknowledged count between the two so an accumulating class cannot become a silent second starvation.

Note: the external-state machinery (external_state_status, classifier, job_dead_letter_events) that would produce Missing rows does not exist yet — this lands the rule that governs them, implementable today, plus the reserved jobs.missing. job_type prefix convention for those producers (declared on JobDeadLetter).

What ships (3 commits, TDD order)

  1. feat(api) — expand-only migration AddDeadLetterTriage: three nullable columns on job_dead_letter (triaged_at, triaged_by, triage_note) + partial index ix_job_dead_letter_untriaged_missing on (failed_at) WHERE triaged_at IS NULL AND job_type LIKE 'jobs.missing.%'. No NOT NULL, no drops: rolling-deploy safe (ci-migration-expand-contract green). "Triaged" means triaged_at IS NOT NULL. No writer exists before A5 — Staff job-visibility dashboard #636 — deliberate.
  2. test(api) — the four failing-first specs + the inert seams they compile against (public CountUntriagedMissingRowsAsync seam, sample field). Proven RED on this exact tree (transcript below).
  3. fix(api) — the behavioral fix: retention delete predicate gains (triaged_at IS NOT NULL OR job_type NOT LIKE 'jobs.missing.%'); every pass logs how many rows it HELD back; the monitor counts the same predicate inside its existing single-statement sample (dlq_metrics CTE), adds it to the structured sample log, emits gauge jobs.dlq.untriaged_missing, and raises a separate dlq_untriaged_missing breach — re-alerting every sample while > 0, recovering at 0, same anomaly semantics as dlq_growth.

Non-goals (per issue thread + design): no API/front surface (that is #636 Phase 4; no contract change, no client regen), no classifier/status machinery, no webhook alerting (WARNING log remains §7.2 v1's hook).

Paired proof (RED without the fix, GREEN with it)

Method: the test commit (fd171b647) contains the specs and compile-only inert seams but none of the behavioral changes; the fix commit (640f23405) differs from it ONLY by DeadLetterRetentionHandler.cs + JobQueueMonitorService.cs. Both runs below executed against the exact committed trees, on the Testcontainers PostgreSQL integration suite (just test-api harness), env APP_ROLE=api ASPNETCORE_ENVIRONMENT=Testing.

RED — commit fd171b6 (tests, no fix)

[xUnit.net 00:00:21.16] PublyApp.Api.Infrastructure.Jobs.JobQueueMonitorServiceSpec.ItShouldSampleAndEmitTheUntriagedMissingCountUntilTriaged [FAIL]
[xUnit.net 00:00:21.16]   Expected held.MissingTriagedCount to be 2L because two durable untriaged missing-anomaly rows are counted exactly, but found 0L (difference of -2).
[xUnit.net 00:00:21.16] PublyApp.Api.Infrastructure.Jobs.JobQueueMonitorServiceSpec.ItShouldAlertWhileUntriagedMissingRowsAreHeldAndStaySilentAtZero [FAIL]
[xUnit.net 00:00:21.16]   Expected monitor.EvaluateAndAlert(JobQueueSample.Empty with { MissingTriagedCount = 1 }) {empty} to contain "dlq_untriaged_missing".
[xUnit.net 00:00:21.18] PublyApp.Api.Modules.Jobs.Jobs.DeadLetterRetentionHandlerSpec.ItShouldHoldAnUntriagedMissingRowBeyondTheHorizonAndReportIt [FAIL]
[xUnit.net 00:00:21.18]   Expected (verify.JobDeadLetter.AnyAsync(d => d.JobType == untriagedMissing)) to be True because an untriaged missing-anomaly row is held however old it is, but found False.

Failed!  - Failed:     3, Passed:     1, Skipped:     0, Total:     4, Duration: 298 ms - PublyApp.Api.Tests.dll (net10.0)

The one passing test (ItShouldDeleteATriagedMissingRowBeyondTheHorizonLikeAnyOtherRow) passes vacuously without the fix: the unfixed sweep deletes everything past the horizon indiscriminately. It becomes meaningful paired with the hold test above (which fails RED precisely because an untriaged row was deleted).

GREEN — commit 640f234 (fix applied)

Passed!  - Failed:     0, Passed:     4, Skipped:     0, Total:     4, Duration: 163 ms - PublyApp.Api.Tests.dll (net10.0)

Verification

  • ~/ai-orchestration-playbook/tools/heavy.sh just test-api on the final tree: 1922/1922 passed, exit 0 (~4 min).
  • OpenApiContractSpec filter (the only dotnet test CI itself runs): 4/4 passed.
  • just ci-drift ✅ · just ci-migration-expand-contract ✅ (18/18) · pnpm lint ✅ 0 errors · pnpm format (oxfmt --check) ✅.
  • Migration snapshot regenerated via just db-add AddDeadLetterTriage (EF 10.0.7 pin); expand-only shape asserted by AddDeadLetterTriage.Spec.cs against real Postgres (columns nullable, index filter present).
  • Front untouched: no typecheck/test/e2e obligations from this diff; local e2e intentionally not run per lane policy (CI front-e2e remains the evidence for anything it still covers).

Unverified / honest limits

  • No operator path exercises this in production yet. Until A5 — Staff job-visibility dashboard #636 ships the staff surface (and the external-state producers land), every row has triaged_at IS NULL, no producer writes jobs.missing.* types, so the held count is structurally 0 in production and dlq_untriaged_missing cannot fire outside tests. The rule and its alarm are proven by specs, not by a live anomaly.
  • Log-based alert delivery is not exercised end-to-end: the WARNING lines are asserted through the returned breach codes and capturing-logger specs, but whatever routes worker logs to a pager downstream of the process is out of scope here.
  • just knip fails, identically on current develop (22 unused files / 9 unused deps, all front/scripts-ts). Pre-existing, unrelated to this diff, not fixed here to keep the lane scoped.
  • The retention exemption uses a LIKE 'jobs.missing.%' marker convention documented on the entity rather than a status column; if a future producer ever stamps an anomaly row WITHOUT the reserved prefix, the exemption will not protect it. The prefix discipline lives in JobDeadLetter.MissingJobTypePrefix docs and the docs: jobs & worker infrastructure design #852 design; there is no DB constraint forcing producers to honor it.
  • Batch-bound sweep loop re-runs CountUntriagedMissingRowsAsync once per pass (one extra indexed COUNT per daily run) — measured negligible, not benchmarked.

Three nullable acknowledgement columns on job_dead_letter (triaged_at,
triaged_by, triage_note) plus a partial index on (failed_at) filtered to
untriaged missing-anomaly job types. Expand-only: no NOT NULL, no drops,
safe under rolling deploy. "Triaged" means triaged_at IS NOT NULL; no
writer exists yet (the #636 staff surface will stamp it), so the columns
stay NULL for every row until an operator acknowledgement path ships.
The reserved 'jobs.missing.' job_type prefix is declared on the entity
for the future external-state anomaly producers.
Failing-first specs (proven RED on this exact tree: 3 failed / 4 total —
transcript in the PR body) for #864/K-2:

- retention must HOLD an untriaged missing-anomaly row past the horizon
  and report it via CountUntriagedMissingRowsAsync (currently deletes it);
- a TRIAGED missing row must sweep like any other row;
- the monitor must raise dlq_untriaged_missing while any held row remains
  and stay silent at zero (currently no such condition);
- sampling must count seeded untriaged missing rows exactly, emit the
  jobs.dlq.untriaged_missing gauge, and recover once they are triaged.

Also lands the inert seams the tests compile against: the
CountUntriagedMissingRowsAsync public seam and the MissingTriagedCount
sample field (constant 0 until wired). The behavioral fix is next.
…held rows (#864)

Retention sweep (#864/K-2): the delete predicate now exempts untriaged
missing-anomaly rows — job_type carrying the reserved 'jobs.missing.'
prefix with triaged_at IS NULL — so an integrity anomaly cannot age out
of existence at 90 days and silently clear its own alert with nobody
having looked. Triaged missing rows sweep like any other row.

Every pass logs how many held-back rows remain, via the new public seam
CountUntriagedMissingRowsAsync — 'skipped N' is now durable output, not a
silent drop.

Monitor: the dlq_metrics CTE counts untriaged missing rows in the same
single-statement sample; MissingTriagedCount joins the structured sample
log; a jobs.dlq.untriaged_missing gauge makes the held class observable;
a separate dlq_untriaged_missing breach re-alerts every sample while any
row stays held and recovers only when each row is triaged or swept.
Owner decision on #864 (2026-08-01): acknowledgement before retention
eligibility, count alert between flag and #636 operator surface.

Proven paired on real Postgres (Testcontainers): RED 3 failed / 4 on the
previous commit's exact tree, GREEN 4 passed / 4 on this one.
@radandevist
radandevist merged commit e0c20c2 into develop Aug 25, 2026
22 checks passed
@radandevist
radandevist deleted the lane/wt-864 branch August 25, 2026 01:38
radandevist added a commit that referenced this pull request Aug 25, 2026
…ptions (K-1) (#1345)

Closes #863.

Gives Unclassified dead-letter rows (external_state_status = 6) a resolution path: a new staff endpoint `ResolveDeadLetterUnclassifiedForStaff` (permission-gated, `UPDATE`-scoped, ForStaff scope marker + DI manifest entry) resolves a row with 404 for a malformed id, 409 for a non-Unclassified row or a lost race, and an audit-log event; `DeadLetterRetentionHandler` exempts Unclassified rows from retention (ANDed with #864's untriaged-Missing hold, so neither exemption weakens the other); a migration adds the external-state columns with a CHECK on prepared/expires bounds; `DeadLetterResolutionCatalog` pins the catalogue; openapi + kiota client regenerated; design doc under docs/analysis. Rebased onto develop after #1336 with the migration regenerated on develop's snapshot.

Implementer: Ox Alpha (stealth/ox-alpha via Nous Portal, max effort, jcode). Reviewer: tencent/hy3:free (Nous Portal, high) — APPROVED at dd73963; CI fully green (28 checks) at that tip. Follow-ups filed: no producer writes status 6 yet (the classifier lane closes the #863 starvation loop); orphan `DeadLetterResolvedSuccess` response key; design-doc handler names drifted from the shipped names. Unverified: nothing beyond CI.
radandevist added a commit that referenced this pull request Aug 25, 2026
…ventory (#1264) (#1326)

Closes #1264

## Summary

Resolves every React Compiler compatibility follow-up queued in the #1234 skip inventory, then rebases onto a develop that advanced three times mid-flight (#1305, #1310, #1314, #1323/#1325 on the front side, plus API/scripts-only lanes). Regenerated against the final tree: **13 diagnostics across 9 files** (down from 36 across 20 when this branch started); **compiled client modules rise from 90 to 97** against the pinned floor of 72.

Per item (one commit each, red/green evidence under `.dump/` by name):

1. **`useWatch()` in `_create-post-drawer.tsx`** — replaces the render-time `watch('body')` read (IncompatibleLibrary).
2. **Shared `useLanguageKeyedZodResolver` hook** — replaces the `[i18n.language]`-memoised resolver + eslint-disable pattern in 7 files (Suppression family).
3. **`profiles.tsx` / `profiles-new.tsx`** — latest-callback ref indirection collapsed to plain functions; ref writes moved out of handlers into an open-transition effect (probe-verified: ref writes inside a handler passed to the column factory taint it); `lastEditedProfileRef` became state.
4. **Big forms render-time ref reads** — `invitations/new.tsx`: known-profile-names map → state with idempotent effect folds plus synchronous per-render union of fresh rows; redirect timer → state-armed deadline owned by an effect; saved-flag re-baseline via synchronous form reset before navigate. (`staff-users/$userId-edit.tsx`, originally covered here too, was later superseded by develop's #1314 — see Rebase record.)
5. **Preserve-memo drops** — `$tenantId/users.tsx` + `invitations.tsx`: manual useCallback/useMemo wrappers deleted; the compiler caches per value.

Plus an inventory refresh in `docs/guides/front/react-compiler.md` (every row dispositioned) and a gate commit clearing all react-doctor findings in touched files (HARD gate: findings must be fixed or suppressed with per-line rationale).

## Rebase record

Three rebases onto a moving origin/develop. Each resolution kept both intents where they coexisted and preferred develop's implementation where it already superseded ours:

1. **Rebase 1 — `_profile-form-drawer.tsx`:** resolved to develop's side entirely. Develop's rewrite moved form ownership to the host page and deleted the very effects/suppressions our commit targeted; our hunk had nothing left to preserve.
2. **Rebase 2 (+#1310 no-iife, #1314 refs-out-of-render, #1323/#1325 DataTable exemption) — `staff-users/$userId-edit.tsx`:** took develop's side everywhere. #1314 implements our item 4 better (module-level snapshots written outside render, adjust-state-during-render absorption, snapshot-based nav blocker), and it untaints the submit closure our version left flagged. Our parallel item-4 work in `invitations/new.tsx` survives unchanged.
3. **Rebase 3 (+#1315 uploads, #1328 launcher — API/scripts only, zero front overlap):** completed clean after discarding a locally regenerated `routeTree.gen.ts` Register block (local toolchain artifact; committed versions intentionally lack it).

Net effect on the skip inventory, recorded honestly in the refreshed doc:

- **Fixed elsewhere:** `$userId-edit.tsx` now compiles outright (#1314); `$tenantId-edit.tsx`'s Refs diagnostic stopped firing after #1305's QueryDisplay restructure; one `_assign-members-drawer` suppression went quiet; the erroneous `__root.tsx` row (its ref write was already effect-wrapped) was dropped.
- **Regressions owned:** #1305 reinstated try/finally in three handlers #1234 had fixed — `staff-users/$userId.tsx` (×2), tenant `users.tsx`, tenant `users/$userId.tsx`. They are re-queued in the inventory rather than papered over.
- **Omission corrected:** `_profile-edit-details-drawer.tsx`'s exhaustive-deps suppression predates this refresh but was missing from the earlier inventory; it is now listed as an acceptable skip.

## Gates

- Artifact guard on the final tree: **97 ≥ 72** compiled modules, runtime chunk present (`compiler-runtime-CNG2r3iR.js`), exit 0 (`check-react-compiler.mjs`)
- Full front suite under the heavy lock, post-rebase: **220 files / 2393 tests passed**, exit 0 (evidence tail `.dump/rebase2-final-suite.log`); typecheck clean
- Root lint gate (oxlint + disable-comment audit + frontend barrels) exit 0 · oxfmt check exit 0 · `just ci-drift` exit 0
- `react-doctor@0.9.12 --scope files --base origin/develop --blocking warning` exit 0
- Skip-inventory parser suite (`packages/scripts-ts`): 7/7 green
- Design-system guard (611 files, 0 violations) and z-index guard (16311 candidates) green inside the suite run

Implementer: Ox Alpha (stealth/ox-alpha via Nous Portal, max effort, jcode). Reviewer: pending adversarial review.

## Round 1 fix

Review r1 (APPROVED_WITH_FOLLOW_UPS) flagged the added `react-doctor-disable-next-line` comments as guard-loosening residue of the clearing commit. Disposition, per the repo rule that added suppression comments are themselves a finding:

- **Audited against origin/develop, not assumed.** A scratch worktree of origin/develop (@301f2b835) scanned with the pinned `react-doctor@0.9.12` shows the annotated effect patterns (open-transition resets, dirty-flag uplinks, click-handler navigate) ARE flagged on develop itself — genuinely pre-existing findings, not introduced by this diff.
- **Kept only under the sanctioned exemption**, each now carrying an explicit per-line rationale (deliberate pattern + pre-existing on develop); `profiles.tsx` restores develop's own rationale block verbatim. No code changes: the fix commit is a comment-only diff.
- **react-doctor before/after:** with the suppressions stripped entirely, `just react-doctor` (`--scope files --base origin/develop --blocking warning`) fails on those pre-existing findings surfaced in the touched files; with the rationales in place it exits 0 with zero new findings. Whole-repo score: develop 66 → this branch 68 (this PR's real fixes raise it; no regression).
- **Re-validated after the fix:** production build + artifact guard (97 ≥ 72 compiled modules, runtime chunk present), full front suite under the heavy lock (exit 0), front typecheck, root lint, oxfmt check, `just ci-drift` — all exit 0.

**Mid-flight develop advances handled:** the first fix push went red because origin/develop moved (#1329 anti-slop rungs 4+5 enabling `anti-slop/no-chained-type-assertions` at error, plus #1312/#1331/#1332); the merge-ref CI therefore flagged four chained-assertion sites in files this PR touches. Rebased onto origin/develop (clean, one overlapping file) and fixed them per #1329's own recipe — no suppressions, no config change: the resolver hook's `as unknown as` chain became a single cast seam, and the three `ROUTE_COMPONENTS` entries of `trans-render.guard.test.tsx` (pre-existing on develop) were narrowed via a local helper. That state went **fully green** in CI (`48a0d8c0a`: quality, react-doctor-gate, supply-chain, all four front-e2e shards). Develop then advanced again before merge: #1335 (staff profile edit surface) touched the same guard-test file with its own equivalent `routeComponentThunk` helper, and #1336 moved jobs code, leaving the PR CONFLICTING. Rebased once more; the sole conflict (`trans-render.guard.test.tsx`) was resolved to **develop's version verbatim** rather than keeping a second competing helper, so the top commit now reduces to the resolver-hook fix alone. That state went fully green locally and was pushed (`a7161537a`): CI came back green everywhere except one shard — `front-e2e (4/4)` died at 43 s inside the **Pull stack** step on a GHCR network timeout pulling the third-party `shopify/toxiproxy` image (`context deadline exceeded`), before a single test executed; the other three shards passed, so this was an infrastructure flake, not a diff regression. Meanwhile develop advanced yet again (#1343 adds the `publy/no-never-any-casts` rule, #1338 the NuGet-audit record — no file overlap with this PR). Final rebase onto that tip and full re-validation: root lint (both anti-slop rules active), front typecheck, oxfmt, `just react-doctor`, `just ci-drift` — all exit 0; targeted trans-render suite (27/27), production build with the artifact guard (97 ≥ 72 compiled modules), and the full front suite under the heavy lock — all green.

Round 1 fix implementer: Ox Alpha (via Nous Portal, max effort, jcode).

## Round 2 fix

Review r2 returned APPROVED_WITH_FOLLOW_UPS for exactly one reason: the round-1 fix had kept suppression comments where it could not immediately root-cause the finding. This round removes every added suppression by fixing the underlying pattern instead — zero suppression-type lines added vs origin/develop:

- **`_change-email-dialog.tsx`** — the open-transition reset effect is gone, replaced by a keyed wrapper: an outer shell increments `sessionKey` on every closed→open transition and remounts a pure `Inner` form through `key`. A fresh mount initialises from `defaultValues` by construction, so no imperative reset exists at all.
- **`_invite-user-drawer.tsx` / `_profile-edit-details-drawer.tsx`** — the dirty-flag uplink no longer compares watch-stream snapshots against a component-captured pristine baseline (the root of the `no-ref-initializer-runs-on-every-render`, `no-pass-live-state-to-parent` and `exhaustive-deps` findings). Dirtiness now comes from react-hook-form's own synchronous dirty computation (`control._getDirty`), which always answers "differs from the pristine session seed". The partial-failure reseed pins its baseline with `keepDefaultValues: true`, so retried failed rows still count as unsaved user data. The profile drawer keys sessions on `${sessionKey}:${profile.id}` so switching profiles remounts while a same-id refetch does not.
- **`use-language-keyed-zod-resolver.ts`** — doc comment reworded so it no longer contains the literal text `eslint-disable-next-line react-hooks/exhaustive-deps -- [i18n.language]` (grep-proof pollution only, no behaviour change).

**Suppression proof:** `git diff origin/develop --unified=0 | grep '^+' | grep -E 'react-doctor-disable|eslint-disable|@ts-expect-error'` returns **no matches** across the whole diff (transcript: `.dump/round2-suppression-proof.txt`; full-suite log tail: `.dump/round2-full-suite.log`). `react-doctor@0.9.12 --scope files --base origin/develop --blocking warning` exits 0 with **zero findings**.

**Gates after round 2:** front typecheck clean; production build + artifact guard **97 ≥ 72** compiled modules with runtime chunk present; full front suite under the heavy lock **224 files / 2436 tests passed**; three targeted suites **35/35**; root lint gate (oxlint + disable-comment audit + barrels) exit 0; oxfmt check exit 0; `just ci-drift` exit 0.

Round 2 implementer: ox-alpha (max effort, jcode).

## Unverified / blocked-by-infra

- All non-e2e checks are green on ad6116f (17 pass). CI-side front-e2e (4 specs) has not run locally by design; on CI it is currently **blocked by an owner-level GitHub Packages problem, not by this diff**: the `Push stack images to GHCR` step fails deterministically (`permission_denied: The requested installation does not exist`) before any test executes — reproduced across 5 runs (initial + 3 reruns + 1 fresh run), while image-content-neutral lanes succeeded in the same window with the same actor. The `publyapp-e2e-*` container packages resolve with `"repository": null` (unlinked from this repo), so token-authenticated uploads of new layers are refused. Full transcript: `.dump/round2-front-e2e-infra-failure.txt`; tracked in #1397 for the owner.
- API suites were not re-run: this branch touches no API code (front-only diff, verified via merge-base file list).



## Rebase after #1396

Develop merged #1396 (GHCR namespace + repo path repoint after the move to the PublyApp org) plus nine more commits (#1382/#1349, #1402, #1380, #1399/#1391 EF Core 10.0.11, #1351, #1358, #1355 specs, #1353), leaving this branch CONFLICTING again. Rebased onto `origin/develop` @ `f2811483a`; reviewed round-3 tip was `ad6116f97`, new tip is **`d7139e212`** (pushed with `--force-with-lease`). Exactly one conflict across the 14 rebased commits, in `docs/guides/front/react-compiler.md`: develop's side of the decision-vocabulary paragraph still pointed “follow-up” at #1264 as an open queue, while this lane's regenerated inventory (which auto-merged everywhere else in the same file) uses the post-#1264 vocabulary. Resolution keeps this lane's updated paragraph, verified against both sides in full before editing; no `--ours`/`--theirs` anywhere. #1396's workflow/package changes applied cleanly with no overlap. Lockfile untouched by develop, so no reinstall was needed. Full local re-validation on the new tip, all green: front typecheck exit 0; root lint gate (`just check-write`) exit 0; full front suite **224 files / 2263 tests passed** (exit 0); design-system guard 0 violations across 620 files; z-index guard OK; React Compiler artifact guard **97 ≥ 72** compiled modules with runtime chunk present. Full record: `.dump/rebase-report.md`.


## Rebase after #1396 again (#1385/#1381/#1360/#1403/#1318), tip 1b3abb1

Develop advanced again while the PR sat reviewed (#1385, #1381, #1360, #1403, #1318), leaving it CONFLICTING a third time. Rebased all 14 commits onto `origin/develop` @ `933319f3f`; reviewed tip `ad6116f97` → first-rebase tip `d7139e212` → **new tip `1b3abb19e`** (pushed with `--force-with-lease`). Five replay stops conflicted, all from develop's #1318 no-giant-component extraction of the giant staff forms; every resolution keeps both intents by taking develop's extracted structure and porting this PR's compiler work into its new home — no `--ours`/`--theirs` anywhere:

- **Item 2 (language-keyed resolver):** `tenants-new.tsx` keeps develop's thin wrapper; the resolver-hook change moved into `_tenants-new-form.tsx` (manual `useMemo(zodResolver(...), [t, i18n.language])` → `useLanguageKeyedZodResolver`, max-seats ref-getter preserved). `$tenantId-edit.tsx` and `$tenantId/users/$userId-edit.tsx` keep develop's extracted-section bodies; import-level conflicts resolved to exactly what the merged bodies use (the hook import in, retired-inline-layout imports out).
- **Item 3 (latest-callback collapse):** `profiles.tsx` keeps develop's thin page over `_use-profiles-list-state.ts`; the item-3 mechanics were ported into that hook — `lastEditedProfileRef` became render-read-safe **state**, the bypass re-arm + PUSH bookkeeping moved into an effect on the open transition, and the `openEditDrawerRef` indirection + columns `useMemo` were dropped for a plain handler handed straight to `makeTenantProfileColumns`.
- **Items 4–5:** auto-merges verified intent-preserving (`profiles-new.tsx` hasSavedRef removal; plain handlers + unwrapped columns in `invitations.tsx`/`users.tsx`).
- **Gate + review-r1 comment commits:** `profiles.tsx` conflicts resolved to develop's side — the one carried suppression targeted inline `openEditDrawer` calling `navigate()` directly, a pattern that no longer exists under the hook architecture (`openEditDrawer` now calls `pushSearch`), so there was nothing to suppress; the drawer files' “Pre-existing on develop” rationales merged cleanly.

Full local re-validation on `1b3abb19e`: `pnpm install --frozen-lockfile` (develop had moved the lockfile), front typecheck exit 0, **full front suite green** (design-system guard 0 violations / 718 files, z-index guard OK, React Compiler artifact guard **97 ≥ 72** compiled modules with runtime chunk present — the ported work survives develop's refactor), `just check-write` exit 0, `just react-doctor` zero findings. Full record: `.dump/rebase-report.md`.

Pushed CI on `1b3abb19e`: **all checks green** — 27 pass, spec-drift skipping as designed for a front-only diff (front-e2e 4/4 under the org namespace), zero failures.


## Rebase after #1396, final take (#1384/#1411), tip 6372398 — all green

Develop moved again while take 1 was green in CI: #1384 (pristine-save guard + shared `resolveProfileSaveFailure`) rewrote exactly the tenant profile-edit drawer this PR refactors, plus #1411 (DLQ backend, no overlap). Rebased once more onto that tip; **new tip `6372398b3`** (`--force-with-lease`). Three replay stops conflicted, all in `_profile-edit-details-drawer.tsx`, all resolved keeping both intents: the gate/r1 comment conflicts keep develop's already-justified suppressions with r1's "pre-existing on develop" wording merged in; the round-2 conflict keeps #1384's new save/failure handling while restoring r2's render-phase `seededFor` reseed and event-driven `methods.watch` dirty uplink (zero suppressions introduced — net removal vs pre-#1384 develop).

Local gates on `6372398b3`: typecheck exit 0; full front suite green (React Compiler artifact guard 97 ≥ 72 compiled modules); `just check-write` exit 0; `just react-doctor` zero findings. Lockfile untouched this time, no reinstall needed. Full record: `.dump/rebase-report.md`.

Pushed CI on `6372398b3`: **all checks green** — 27 pass, spec-drift skipping as designed for a front-only diff (front-e2e 4/4 under the org namespace), zero failures; GitHub reports the branch **MERGEABLE**.



---
Implementer: Ox Alpha (Nous Portal, jcode, max effort). Reviews: r1/r2 (free chain), r3 APPROVED at ad6116f, r4 APPROVED by tencent/hy3:free at 6372398 after three rebases onto develop (deltas attributable to #1318/#1385/#1384; 14-commit intent intact; 0 suppressions added); CI 27/27 green incl. front-e2e 4/4.
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.

jobs: job-dead-letter-retention deletes 4 Missing rows at 90 days, silently clearing their alert without triage

1 participant