Merge NEXT: planning, generation map and portable configuration on top of the OIDC access model

NEXT predates the access work, so the identity layer follows master: access comes from
configured groups, instance administrators replace the site administration, and accounts are
created by SSO or by an administrator. Kept from NEXT: manual publication with the Review
changes panel and shared generator drafts, portable import/export (meguri/v2), the layered
generation map with binding provenance, JSM provider changes, contact/approval semantics and
the account-owned Person. Dropped: site administration, local sign-up, and the mandatory
unique email with its contact/account migrations. The manual-publication migration is
renumbered m_0010 after m_0009_access.

Accounts without an email still get a Person in every workspace they reach, a changed sign-in
email updates the account's person, and an account that loses access keeps its linked person so
schedules stay valid. Instance administrators may use Sync and shared drafts.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Aleksandr BogomiakovandClaude committed 2026-10-07 16:06:10 +01:00
commit 38105c65c2
57 files changed
+6890 -431

No files matched your search

+66 -4
View File
@@ -29,17 +29,23 @@ Meguri is a multi-tenant on-call configuration service. Workspace is the single
- Country holidays do not interrupt ordinary 24/7 rotations. Absence makes an employee unavailable in every schedule, including single-participant rotations and approved replacement assignments. Uncovered assignments remain visible as diagnostics and block future publication until an available replacement is arranged; the provider must never receive a new plan assigning an absent employee.
- Absence dates are inclusive and refer to duty days beginning at each schedule's handover, not midnight. An absent general on-call user is reported as a coverage problem; there is no invented automatic replacement.
- Absence planning accepts a small list of half-open date/time ranges and replacement people, scoped to one schedule or all affected schedules. Legacy API duty-day ranges remain supported. Each range replaces only the absent employee's actual affected shifts across rotations. Project coverage uses its existing secondary/fallback automatically; an optional range setting uses the selected replacement for affected project coverage too. Unrelated owners and base rotation anchors remain unchanged. The server previews actual before/after intervals, automatic changes, proposed replacements, gaps, and direct/team escalation warnings before saving.
- Absence is recorded immediately. Explicit replacements are grouped into requests per range, with one confirmation covering all affected schedules; they become effective only after all required approvals. Leads can force them with an optional comment. Saving absence, contacts, pending/applied requests, audit, history, and outbox is atomic and revision-checked. Approval revalidates ownership, availability, and overlaps. Pending replacements cannot make an incomplete plan publishable; existing external configuration remains until successful publication. Direct user/team escalation recipients are outside schedule substitution and are highlighted explicitly.
- Absence is recorded immediately. Explicit replacements are grouped into requests per range, with one request covering all affected schedules; they become effective after all required account-holder approvals, or immediately when none are required. Leads can force them with an optional comment. Saving absence, contacts, pending/applied requests, audit, history, and outbox is atomic and revision-checked. Approval revalidates ownership, availability, and overlaps. Pending replacements cannot make an incomplete plan publishable; existing external configuration remains until successful publication. Direct user/team escalation recipients are outside schedule substitution and are highlighted explicitly.
- Replacements may cover any future interval, or the remaining part of a current shift. A reciprocal swap is one request containing two legs and applies atomically. Its approval must come from the other affected employee. A lead may apply without confirmation using the force grant. The action comment is optional, including forced application.
- An interval ends exactly when the next starts. An override does not alter the rotation sequence. Overlapping approved overrides are rejected.
- Local wall-clock handovers follow IANA timezone rules. Ambiguous or nonexistent handovers are validation errors. Historical assignments are retained through dated configuration revisions and approved override records.
A member may give their own shift or volunteer to cover a colleague; every non-forced leg must involve the actor. All other affected people approve. Current-interval save and approval clip to the server clock without rewriting history. Fully expired ordinary replacement/swap legs prevent the entire application. Approval-based requests require workspace access for both participants; contacts need a lead's explicit forced action.
A member may give their own shift or volunteer to cover a colleague; every non-forced leg created by an ordinary member must involve the actor. All other affected participants with workspace accounts approve. Email-only planning contacts never approve or receive access. Current-interval save and approval clip to the server clock without rewriting history. Fully expired ordinary replacement/swap legs prevent the entire application. Contacts are excluded from required approvers. A validated request with no remaining approvers applies immediately without being marked forced. Schedule editors may plan changes involving contacts on their behalf; ordinary members still may only give their own shifts or volunteer. Existing login accounts lacking workspace access are not treated as contacts.
Every action has an optional comment (for example, a task number or vacation). It appears on the approval request and in history, not as a direct message. Empty comments stay empty.
## Provider boundary
JSM team IDs accept either a UUID or `og-` followed by a UUID. Preserve the full ID from the Operations team URL, including its prefix; the cloud ID remains a UUID.
JSM resource names start with the manual or generated resource name and end with a stable `[meguri:<ownership digest>]` marker. Renaming retains remote identity. Existing technical names are recognized during recovery; name migration does not bypass drift checks.
JSM publication preserves arbitrary minute/second boundaries from the publication clock forward through managed schedule overrides when native rotations cannot represent the plan. The current assignee is exported as the base rotation (or `noone` before coverage starts); future assignments and uncovered gaps use exact overrides. JSM rejects new overrides starting in the past. Local history remains unchanged. The provider has millisecond precision; finer historical timestamps are truncated only in its wire representation, with shared boundaries transformed identically. Intervals that collapse at this precision are rejected before writes. Native rotations remain available for half-hour-aligned plans. Provider-managed overrides are distinct from manual external overrides and do not alter approval state in Meguri.
Meguri computes the assignments and manages escalation and routing configuration. Providers execute alerts, notifications, escalation delays, and acknowledgment. Meguri has no incident processing loop.
The JSM adapter must support schedule identity mapping, generated coverage, escalation steps and repeats, and routing rules. Advanced escalation options depend on the external plan; unsupported combinations must fail visibly. No credentials are stored in workspace documents.
@@ -87,7 +93,7 @@ Alice, Bob, and Carol rotate weekly from Monday at 11:00. Payments primary is Al
Project escalation: primary at minute 0, secondary at minute 10, all team members at minute 30, if unacknowledged. General escalation: on-call at minute 0, random team member at minute 30, named lead at minute 45. Both may repeat 10 times, reset recipient states, and leave the alert open. Working-hours routes select project policies on weekdays 11:00–19:00; a catch-all route selects the general policy. Development alerts may be routed to no notification.
## People, calendars, and working intervals
Scheduling people are separate from login accounts. A workspace can select its members (people who reached it through groups) or add an email-only contact without creating an account or granting access. Their name, email, and ISO country belong to the versioned workspace state. Country defaults to Cyprus (CY); the UI offers all 249 ISO country codes. Existing login IDs remain stable and an email never grants access; at SSO sign-in it can bind a contact to the signing-in user (see Access and administration). Only authenticated, authorized users can approve requests; an email-only recipient requires an administrator's explicit forced application until that person signs in and the contact is linked. Selecting a workspace user does not grant them workspace access.
A login account owns its canonical Person; planning-only people have no login account. A workspace can select its members (people who reached it through groups) or add an email-only contact without creating an account or granting access. Workspace state retains versioned snapshots of name, email, and ISO country; for account-owned people these fields come from the account profile. Country defaults to Cyprus (CY); the UI offers all 249 ISO country codes. Existing login IDs remain stable and an email never grants access; at SSO sign-in it can bind a contact to the signing-in user (see Access and administration). Only authenticated, authorized users can approve requests; email-only contacts are planning entities and require no approval of their own. Selecting a workspace user does not grant them workspace access.
Each schedule has its own local handover time (11:00 by default) in the workspace's IANA timezone. Duration is still measured in calendar days. Dependencies resolve their own handover boundaries. Workspace working days, start/end time, and IANA timezone are shared by all routing rules with “Only during working hours” enabled. Changing this workspace interval atomically updates workspace snapshots/revisions and queues publication. Old migrations preserve historical handovers and timezone values.
@@ -190,10 +196,66 @@ Variable editing uses a bounded, progressively disclosed tree: only the root sta
Generation map opens from Generation rules as a read-only, draggable/zoomable Cytoscape canvas. Server preview returns actual iteration counts, input contexts, predicate outcomes and produced IDs; no scheduling evaluation is duplicated in JavaScript. Iterated outputs start collapsed into a group; inspecting a node or pass reveals source data and verified resources. Invalid drafts show diagnostics rather than stale resources. The map is a captured draft preview refreshed explicitly, and never applies changes. Bundled Cytoscape 3.33.1 is served locally with its MIT license; no CDN or new framework is required. Browser coverage checks groups, details, source navigation, pan/zoom and mobile layout; engine regression verifies traced pass results.
The generation map arranges numbered rules in saved execution order from left to right, joined by solid sequence arrows. Each rule owns a vertical column of iterations, conditions and outputs. Dashed secondary edges show data dependencies rather than execution order. Repeated output groups use offset background cards; decorative layers follow dragging and do not receive selection. Browser checks verify rule ordering, aligned output columns and stack movement.
The generation map arranges numbered rules in saved execution order from left to right, joined by solid sequence arrows. Variables occupy the upper band in first-consumer order, rules and their iterations and conditions occupy the middle band, and outputs occupy their producer columns in the lower resource band. Dashed edges show data dependencies. Repeated output groups expand into individual resources within the resource band. Browser checks verify rule ordering, band separation, output expansion and manual resources.
Map input details display evaluated server values for each block and pass, never serialized bindings. Shared values are deduplicated across passes; differing values are explicitly counted. Generated references collapse to type/name cards, and people resolve by display name. Empty binding metadata and raw reference UUIDs are not presented. Condition operands and escalation recipients/delays come from the same resolver used during expansion.
The map’s all-passes input overview lists deduplicated upstream sources, not expanded output settings: variables with used binding addresses and resource references grouped by producing block. References internal to the selected whole rule are omitted; selecting an individual output block shows its dependencies. Selecting a concrete pass still exposes its evaluated field values.
Expanded map resources use type-specific labeled fields and the same compact reference cards as inputs, never JSON dumps. Priority schedules show ordered people, fallback and zoned handover; rotations add anchor/cycle; policies show recipient/timing/condition and repeat behavior; routes show conditions, working window and destination. Reference names resolve from the preview snapshot. Browser regression checks visible names, participant order and absence of UUID metadata.
A desired JSM resource deleted externally is recreated even with drift blocking enabled. Existing resources modified externally still require the configured drift policy. Missing managed overrides are restored; unmanaged resources are never adopted by ID guessing.
## Explicit publication and shared drafts
Saving workspace configuration, approving replacements, importing data and editing generator drafts never authorize provider writes. These changes persist in PostgreSQL and are visible to other authorized members. Ordinary unfinished modal forms remain local until Save. Generator drafts autosave separately, including invalid intermediate values, with their own optimistic revision and workspace scope.
The top Review changes panel compares desired resources with the last successfully published document. Its Sync button is yellow when unpublished changes exist. Sync validates/applies the shared generator draft and explicitly authorizes a specific workspace revision; stale reviews fail. Provider status, errors and create/update/remove counts live in this panel. Failed authorized publications may retry the same snapshot; newer saved changes must never enter those retries. Successful jobs do not automatically refresh their horizon: the displayed coverage end requires a further explicit sync to extend it.
The worker persists the authorized document and publishes that snapshot, not the newest workspace document. A click while a publication lease is active is rejected. Existing jobs are paused by the manual-publication migration until an explicit click. Published snapshots survive history pruning. Tests cover no writes before a click, pinned retries across later edits, no periodic writes after success, scoped shared-draft persistence, stale concurrent edits, resource review against the published baseline, and the browser workflow across sessions.
## Compact recurring provider rotations
A plain base rotation whose effective published coverage matches its ordered calendar-day cycle is exported to JSM as one finite recurring rotation named after the schedule. The engine supplies the current cycle phase, ordered participants and calendar-day shift length; the provider only translates these fields to daily/weekly API values. The remote schedule ID and ownership marker stay unchanged. A publication replaces the old per-shift rotations rather than accumulating new ones. Native boundary precision must be representable; irregular effective coverage and replacements retain the exact interval export. The canonical expanded timeline remains available for verification and preview. Tests cover mid-shift starts, pre-anchor phases, participant ordering, DST, replacements, migration of an existing owned schedule and idempotent repeated publication.
When an owned JSM schedule has one current and one desired rotation, edits preserve the rotation ID through the dedicated rotation PATCH endpoint. Schedule metadata is patched without a rotations array. Verified intermediate writes are checkpointed; a durable pending-step snapshot recovers a lost response without mistaking that exact planned state for external drift. Tests model nested rotation replacement by schedule PATCH and require stable rotation identity across participant reorder, rename, retry and unchanged republish. Historical rows already retained by JSM are not deleted by this repair.
Workspace state save/reload controls live beside Sync in the top Review changes panel. The localized hint distinguishes shared draft autosave, validated local application, and explicit publication; Sync includes application, so a separate save is optional. Reload retrieves the shared draft, not a historical rollback. Removing a variable or generation rule requires confirmation before mutating or autosaving the draft. Browser regression covers cancellation, confirmation, top-panel controls and narrow layout.
JSM coverage rotation names use the schedule name, including priority coverage and the exact-time base rotation. Multiple finite rotations append the interval start in the schedule IANA timezone with date, time and UTC offset to distinguish DST folds. This is presentation only; ownership markers, coverage, checkpoints and explicit publication remain unchanged. Existing rotation names update on the next authorized synchronization.
Manual and generated routing editors expose the existing greater-than and less-than operations. Priority follows alert severity (P1 highest); P3 and above is represented as less-than P3 with negate=true, including the boundary. The editors show comparison controls without an additional priority instruction; the provider wire contract is unchanged.
Publication status exposes the lease expiry and a server-calculated publication_busy flag. Sync is disabled for an active lease, not merely the persisted publishing state. The review panel explains restart recovery and expired leases; normal polling refreshes eligibility. Restart never clears a live lease. PostgreSQL regression checks active-lease rejection and explicit reauthorization after expiry.
Nested binding paths retain the raw input text while typing, including trailing spaces and slash separators. The parsed path still updates live for shared drafts and server previews. Reopening shows the normalized path; changing source resets it. Browser regression types a nested list path character by character and checks its resolved value, edits, reopening and source reset.
JSM omits `repeat` when escalation repeats are disabled. Read-back and drift comparison accept an absent repeat field only for an expected escalation with `waitInterval: 0` (the provider disabled representation). Present repeat objects and enabled repeats remain strictly compared. Fake-server regressions reproduce omission, recovery without a checkpoint, idempotence and detection of externally enabled repeats. No overwrite policy is needed to recover an otherwise matching escalation.
Sync immediately shows submitting feedback, then stays disabled for an authorized pending job as well as an active publication lease. Pending publication has a visible queue message. The UI polls every two seconds while queued or leased and keeps the twenty-second idle refresh; expired leases remain manually recoverable. A successful sync request updates local queue status before refreshing the workspace. Browser regression holds the queued response and verifies disabled state and automatic completion without reload.
Generated escalation steps support Everyone on the team in addition to schedules and people. The optional team_all flag defaults to false for existing drafts; team recipients do not resolve an individual binding. They compile to Recipient::TeamAll and JSM uses the configured workspace integration team ID with notifyType=all. Delay and acknowledgment condition remain explicit. Generator, wire-contract and browser regressions cover the 30-minute team step and legacy recipients.
## Layered generation map and value provenance
The read-only map uses three separated horizontal bands: variables, generators, resources. Generator columns follow saved execution order; variables sort by first consumer, and generated resources occupy their producer column. Manual resources are included in the resource band with explicit manual attribution. Shapes distinguish bands; resource colors distinguish schedules, escalations and routes. Expanding output groups retains these bands.
Server preview provides binding provenance using the same resolver as generation: source bindings, iteration sources, lookup keys, list items, intermediate path values, final values, node identities and errors. The browser presents this captured snapshot rather than re-evaluating scheduling logic. An inspector popover supports history, source navigation and per-pass examples. Dotted address segments are independently colored, with neutral separators; quoted keys containing dots remain intact. Generated names retain separate bound and literal affix segments, and resource fields link to their binding or explicit default/manual origin. Clicking the map backdrop closes it; clicks on the canvas only clear selection. Tests cover provenance, layout, manual resources, source exploration and safe text rendering.
The value explorer renders source traversal as ordered monospace steps with neutral arrows and a selected-step indicator. Source navigation is a separate footer action; producer fields are independent choices rather than sequential path steps. Browser coverage checks stage values and separate source navigation.
## Portable generation configuration
[meguri/v2](portable-config.md) defines compact JSON import/export (with legacy v1 import support) for variables and generators only. Merge by name retains omitted inputs; nested identity keys preserve resource identity across editing and repeated imports. People transfer by email, manual resource references by exact name, and generated links by generator/block. The server provides a read-only parsed/expanded preview and revalidates before saving the shared draft with workspace and draft revisions. Provider writes require a later explicit Sync. Tests cover cross-workspace remapping, repeated imports, unknown fields, scoped permissions, stale previews, email contacts, parse errors and browser application.
### Compact portable configuration
`meguri/v2` is the default export: ordinary JSON variables, typed wrappers only for special scalar types, compact binding references, flattened output fields, readable output keys and omitted semantic defaults. The v1 importer remains supported. Inactive binding metadata must never be exported. Human output keys persist in shared drafts; list entries reconcile by unique name/value first and position for unmatched edits. The preview remains authoritative for identity-changing edits. Regression checks compare expanded resources, repeat imports and actual size reductions.
The Import / Export dialog includes a localized Guide tab with a complete compact v2 reference, defaults, binding scope, operations, limits, merge semantics and a runnable example covering all output types. Copy guide copies every section as Markdown for hand editing or LLM use, without workspace data. Switching tabs preserves import text and does not apply it. Browser checks verify copying, sample preview, tab preservation and mobile layout.
Generation escalation steps can notify a random member of the workspace provider team. The provider chooses the recipient when executing the step; this is neither a random schedule nor sequential notification of all colleagues.
## Account-owned person
Every login account owns a Person with the same stable ID. An account is the authentication/access extension of that person, not a second scheduling identity. Email-only people remain contacts without login credentials or membership. Workspace documents contain versioned Person snapshots; the account's name, email and country are authoritative and cannot be edited through a workspace contact form, and every regular workspace save refreshes the snapshots of the members. An account's email is optional (password users may have none; SSO accounts take it from the provider) and is not unique: an email shared with a contact links only when exactly one unlinked contact matches (see Access and administration), otherwise nothing is merged. An account that loses access (its groups changed or its workspace was disabled) keeps its person and the link, so schedules and history stay valid; it simply stops being a member and can no longer approve. An unverified or ambiguous email never grants access or merges login accounts.