Skip to Content
Internal docs are powered by Nextra Docs Theme.
SystemsPlatformAPI2Rails to API2 migrationPackagesNative work-schedule writes

Native work-schedule writes

ENG-2742  implements native POST, PATCH and DELETE for /work_schedules. Existing Rails-shaped reads and payload renderers are reused. ENG-2752  moves the three first-party mutation callers to API2 after regional lock deployment and process turnover were verified. SETTINGS still has retained contracts.

Parity and concurrency

Every authenticated tenant member retains the existing policy access. Resolve the numeric tenant from the native session and scope every schedule/source lookup and write. Reject foreign or discarded schedules. A synced schedule allows only the default flag to change; deletion remains a soft discard and clears default without deleting conversation references or holiday calendars.

Preserve Rails timezone labels rather than relying on Node ICU’s different alias set. Read-only checks on September 3 at 11:30 UTC verified the same 634 accepted names in both deployed Rails regions. The original local export had 748 names, including 114 unsupported aliases. Even two Rails mapping labels (Kyiv and Rangoon) fail in the deployed TZInfo environment; their canonical IANA names remain accepted. Test actual ActiveSupport::TimeZone[name] acceptance, not just the union of mapping keys and zone identifiers.

The exporter filters through the actual validator. Its default mode verifies the current environment against the committed snapshot and fails on any drift; it never writes that file. Generate in the deployment’s Rails/TZInfo environment and compare both regional outputs before deliberately replacing the snapshot. Use --stdout for read-only capture:

bundle exec ruby ../rulebase-web/rulebase-api2/scripts/work-schedules/export_timezones.rb --stdout

Normalize supplied interval integers and field/operator/value conditions. Preserve omitted fields, including stored interval precision. Validation returns Rails model field errors with status 400; provider-edit denial is 403, missing/foreign/discarded rows are 404, and discard succeeds with 204.

Rails and API2 share a transaction-scoped PostgreSQL advisory lock derived from work_schedule_writes:<numeric organization id>. Acquire it before reading or clearing other defaults, including an empty source group. The lock serializes all schedule saves within one organization, while default replacement itself stays within the same organization/data-source group. It remains held until commit; nested Rails callback saves can re-enter it. API2 bounds lock waits to three seconds and leaves failed mutations available for a normal client retry.

Evidence

Focused API2 tests use the checked-in Rails table DDL, exercise tenant/source boundaries, normalization, error payloads, synced schedules, default groups, soft deletion and production app authentication. Rails request/model tests verify existing behavior and that the shared lock precedes the default query.

Run the independent PostgreSQL concurrency rehearsal from rulebase-api2:

pnpm exec tsx scripts/work-schedules/rehearse-concurrency.ts

It creates and removes an isolated local database, restores the Rails table, proves server-side contention against the exact Rails lock protocol, runs twelve competing native selections, and checks tenant isolation and rollback. It uses synthetic test environment values and accepts only a loopback /postgres admin URL through WORK_SCHEDULE_REHEARSAL_ADMIN_URL; it never uses DATABASE_URL. This is local protocol proof, not production observation.

Staged caller cutover

  1. Complete normal review/CI, including the Rails model lock and API2 contracts.
  2. Verify the lock-bearing Rails/API2 revisions deployed in both regions and old Rails writer processes drained. Verified September 3 at 11:06 UTC: both runtime lock implementations were present and all eight services had replacement tasks.
  3. Switch the three first-party mutation helpers to API2 in a follow-up caller PR with generated types and focused client tests. Never route callers before both runtimes follow the lock protocol. No temporary feature selector is added.
  4. Observe actual create/default/update/discard behavior in both regions and 24 hours without errors or duplicate defaults before closing the package.

19 focused API2 route tests and four caller tests cover the cutover, including canonical timezones/rejected aliases and list/detail invalidation after each mutation. The prior twelve Rails examples and real PostgreSQL lock/concurrency rehearsal remain decisive protocol evidence; no lock implementation changes here. Actual regional mutations and 24-hour observation remain open. No production data repair, schema change, source edit or job replay is part of this package.

For rollback, move first-party callers to Rails before removing the native routes. Keep the Rails lock until all native writers have drained. Do not revert schedule data or remove compatible public records.

Last updated on