Skip to content

feat(static-loader): retire stale Sections safely#476

Merged
tiankaima merged 19 commits into
mainfrom
agent/stale-section-lifecycle
Jul 17, 2026
Merged

feat(static-loader): retire stale Sections safely#476
tiankaima merged 19 commits into
mainfrom
agent/stale-section-lifecycle

Conversation

@tiankaima

@tiankaima tiankaima commented Jul 17, 2026

Copy link
Copy Markdown
Member

Status

Rollout authorized on 2026-07-18 without waiting for #471; recovery risk is explicitly accepted for this rollout.

Scheduled Static Sync keeps retirement disabled. Enabling retirement requires a manual run with the exact downloaded snapshot SHA-256 and exact expected candidate count.

Problem

Production has 132 Section rows in the four synchronized semesters that are absent from the latest complete snapshot. Hard deletion or heuristic jwId remapping is unsafe because these rows may own subscriptions and user-generated history.

Changes

  • add reversible lifecycle reconciliation using sourceLastSeenAt / retiredAt
  • persist a singleton StaticImportState generated-at/SHA watermark under the import advisory lock; older or same-time/different-content snapshots fail before model writes
  • require an explicit manual bootstrap (and exact SHA for a committed bootstrap) before the first watermark write
  • require a complete schema-v5 snapshot, exact mapped jwIds, strict RFC 3339 generated_at, a 15-minute maximum future skew, and whole-import recency
  • retire only missing in-scope Sections after exact SHA/candidate approval; re-import clears retirement
  • audit retirement/reactivation in the same transaction
  • exclude retired Sections from current catalog/search/calendar/dashboard/sitemap/new-subscription and related-Section discovery
  • preserve direct historical detail and user content; an already-subscribed retired Section can be explicitly retained by replace/set or removed, but cannot be newly subscribed
  • serialize subscription writes against lifecycle state changes with per-Section transaction advisory locks, post-lock eligibility checks, set-based counts, and a commit-adjacent lifecycle lock window
  • expose lifecycle timestamps in the API and label historical Section pages

The separate empty-source SectionTeacher reconciliation bug was fixed and released independently by #478 so it is not held behind this rollout gate.

Verification

  • full unit suite plus focused query/watermark/rollback tests
  • fresh PostgreSQL integration, repeated seed/import, three TypeScript projects, Svelte check, Biome, OpenAPI parity, and production build
  • static-loader image build/smoke and a real schema-v5 snapshot dry-run (6696 Sections / 110955 schedules / 2028 exams)
  • older/future/same-time-conflicting snapshot rejection, exact one-candidate retirement, reactivation, audit rows, and user-content preservation
  • Playwright coverage for historical detail, no new subscription, explicit retired-subscription retention/removal, and existing-user unsubscribe
  • two-client PostgreSQL concurrency coverage for importer-first/subscriber-first ordering and duplicate-ID counts

Deployment boundary

The expand-only Section lifecycle storage migration from #473 was applied by scheduled Static Sync run 29606298861 before cancellation; its Load static snapshot step was skipped, so no lifecycle/backfill/retirement data write ran. DB Migrate Deploy run 29606395044 then confirmed no pending migrations. #475 restored exact Prisma schema parity and released in v1.61.1.

This PR additionally contains an additive StaticImportState table migration. It has not yet been merged or applied. After an approved rollout, ordinary/scheduled writes intentionally fail closed until a manual dry run and exact-SHA bootstrap establish the first watermark.

#474 added repository-side recovery tooling, but it does not prove the provider, retention/PITR window, RPO/RTO, or an isolated restore. The owner explicitly authorized this rollout without waiting for #471; #471 remains open because those recovery criteria are still unmet.

Refs #468
Recovery follow-up: #471

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
life-ustc af33a8e Commit Preview URL

Branch Preview URL
Jul 17 2026, 09:19 PM

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

@tiankaima
tiankaima marked this pull request as ready for review July 17, 2026 21:47
@tiankaima
tiankaima merged commit 3928bf1 into main Jul 17, 2026
15 checks passed
@tiankaima
tiankaima deleted the agent/stale-section-lifecycle branch July 17, 2026 21:47
github-actions Bot pushed a commit that referenced this pull request Jul 17, 2026
# [1.62.0](v1.61.2...v1.62.0) (2026-07-17)

### Features

* **static-loader:** retire stale Sections safely ([#476](#476)) ([3928bf1](3928bf1))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant