Files
meguri/docs/product.md
T
Aleksandr BogomiakovandClaude 38105c65c2 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>
2026-10-07 16:06:10 +01:00

54 KiB
Raw Blame History

Product contract

Meguri is a multi-tenant on-call configuration service. Workspace is the single tenant boundary: there are no nested teams. A user may belong to several independent workspaces. Three roles exist: instance administrators, workspace administrators, and employees. Workspaces and the groups that grant roles are declared in the instance configuration; the application never lets anyone create them.

Access and administration

Instance configuration. Everything about who may enter which workspace is declared in one file read at startup (MEGURI_CONFIG_FILE, YAML; JSON is valid YAML). The process refuses to start without it, except demo mode, which carries a built-in configuration of the same format. Unknown fields and invalid values fail startup with the reason. The file declares: auth.password_login (default false); optional auth.bootstrap_admin.usernames; optional oidc (issuer, client_id, scopes default openid profile email, groups_claim default groups as a dotted path, groups_sources default [id_token, userinfo]); instance_admin_groups; and workspaces, each with a stable key (3–64 lowercase letters, digits and hyphens), a unique name, at least one admin_groups entry and optional member_groups. At least one sign-in method must be configured. The OIDC client secret is a deployment secret (MEGURI_OIDC_CLIENT_SECRET), never part of the file. deploy/instance-config.example.yaml is a commented example.

Roles. An instance administrator sees every enabled workspace, may act in it with every permission without being a member, and manages password users and disabled workspaces from the instance page; configuration, workspaces and group mappings are read-only there. A workspace administrator holds every permission in exactly that workspace and cannot see, create or affect any other. An employee holds the workspace's employee permission set: calendar, schedule editing and replacement requests by default (view, edit_schedules, request_overrides). Administrators change that set in workspace settings and it applies to every current and future employee immediately; view is mandatory and manage_members/manage_integration can never be granted to employees. Granting force_overrides lets every employee apply replacements without confirmation; leaving it out requires approval.

Employees and workspace settings. edit_schedules lets an employee plan: schedules, escalations, routing, absences, holidays and new email contacts. It never covers the workspace preferences (name, planning horizon), existing entries of the scheduling directory, the working interval, the drift policy, the provider integration or access; those are workspace administrator (and instance administrator) actions that employees may view but not change. The server rejects such edits with 403 and the UI hides the controls.

Groups decide roles. A person's groups are compared to the configured groups by exact string match (case, slashes and prefixes matter). Workspace administrator beats employee when both match; matching instance_admin_groups makes an instance administrator. Memberships are recomputed from the stored groups at every SSO sign-in, whenever an administrator edits a password user's groups, and for every user at each configuration sync, so removing a group or a workspace removes access. A sign-in whose groups map to nothing is refused with an explanation and a support reference; no account is created or kept enabled for it.

Accounts. There is no self-registration. Accounts come into being only through an SSO sign-in whose groups map to something, or when an instance administrator creates a password user (username, display name, password of 12–256 characters, optional email, groups chosen from the configured list). SSO accounts have no password and their groups belong to the identity provider (read-only in Meguri); password accounts have groups edited by an instance administrator, who cannot edit their own, and can have their password reset (which ends their sessions). An SSO identity is never linked to an existing password account automatically. Ordinary password sign-in works only when auth.password_login is true.

Workspace lifecycle. On startup the configuration is synchronized: a configured key without a workspace creates one; an unkeyed workspace with exactly the configured name is adopted (several candidates abort startup); a changed name renames it through an ordinary revision; a workspace missing from configuration is disabled, never deleted. A disabled workspace is invisible and inaccessible to everyone including sessions already open, receives no publication and keeps all its data; configuring it again re-enables it with its previous data and queues a publication. A configuration that lists no workspace while enabled ones exist is refused unless MEGURI_ALLOW_DISABLE_ALL=true. An instance administrator may permanently delete a workspace that is disabled and not in the configuration by typing its key (its name if it never had one); history, audit and provider checkpoints go with it, but resources already published to a provider are not removed. Instance-level changes are recorded in instance_audit.

Bootstrap administrators. Optional break-glass accounts listed in auth.bootstrap_admin.usernames are created at startup as instance administrators with a generated password written to the log of the process that synchronized the configuration. The password is derived from a secret held in the database, the username and the current UTC hour, so replicas restarted in the same hour agree; a new hour rotates it and ends the account's sessions. They may sign in even when auth.password_login is false. Removing the block locks the accounts (password cleared, sessions ended, administrator rights revoked) but keeps their records. While enabled, anyone who can read the logs can become an instance administrator; production normally omits the block.

Single sign-on. Authorization code with PKCE for a confidential client. The browser carries a one-time state cookie bound to a database record; callbacks without a matching, unused, unexpired record are refused. ID tokens must be RS256 with a valid signature, issuer, audience, expiry and nonce. Groups come from the configured claim in the ID token, then from userinfo; a missing claim is treated as no groups and logged as a provider misconfiguration. Failures return to the login screen with login_error of no_access, failed or unavailable and a short reference that support finds in the log. Sign-out ends only the Meguri session.

Email linking. The sign-in email is lowercased. In each workspace the user belongs to, an existing email-only contact with that email becomes the user: the contact's ID is replaced by the user's ID throughout the current document (schedules, absences, requests, variables), the old ID is kept as an alias for history and audit, and the contact's name and country are kept. A user without a matching contact gains a person carrying the email, which providers need to resolve them. Ambiguous or conflicting data (several contacts, a contact already tied to another login, a person with a different email) is skipped. An email never grants access; only groups do.

Scheduling

  • Ordered rotations with a local anchor date, local handover time, and a duration in calendar days (weekly defaults to seven).
  • Project primary coverage: the first available person in an ordered responsibility list (not a rotation), then the current effective fallback rotation if everyone is off. Absence and country holidays skip each person independently. The single UI/domain type is Owners by priority. Legacy two-person schedules normalize to an ordered pair when read, including history; IDs, handovers and fallback references are preserved. Legacy generation blocks normalize to priority blocks with an ordered binding list, retaining dynamic primary/secondary expressions.
  • 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 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 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.

External edits are conflicts by default, not silent imports and not silently overwritten. A workspace may opt into overwrite of Meguri-owned resources. Unmanaged external resources remain outside Meguri ownership.

Variables and generation

Workspace variables have arbitrary user-defined names and nested typed values: text, person/email, number, boolean, date, time, duration, list, and dictionary/record. Lists and dictionary entries have stable internal IDs separate from their values and keys. A visual generation rule contains ordered iterations, optional conditions, and schedule/escalation/routing output blocks. Fields bind to constants, variables, loop values/keys, dictionary lookups, fields/list positions, or other outputs; names can add fixed prefixes/suffixes. No executable user code is accepted.

Editing is a draft with debounced server validation and a complete create/update/remove preview. A missing dictionary key, empty participants, duplicate IDs/keys, bad types, invalid references, unavailable permissions, or excessive expansion prevents activation and identifies the offending rule/field/item. Variable changes recompile the consuming rules in the same workspace. Activation requires schedule-edit permission and compares the single workspace revision atomically; no other workspace is read or changed.

Generated resources are ordinary schedules and provider configuration with recorded source ownership. IDs derive from workspace, rule, stable iteration entries, and output block, preserving history/overrides across rename and reorder. Edit their source through the generator, not the individual generated resource. Current/future approved replacements prevent destructive generated schedule removal. Pending requests referencing removed schedules are cancelled explicitly in the previewed change. Generated routing stays in a contiguous ordered block at a chosen position among manual rules. Saved workspace history includes exact source variable snapshots and rule definitions.

Generation is bounded: at most 100 variables, 10,000 data nodes, four iterations per rule, 50 rules, 200 schedules, 200 policies, and 500 routing rules per workspace. Expansion fails atomically on excess; provider limits still apply. Generated contacts use exact existing workspace-email identities or deterministic email-contact IDs and never gain login/workspace access.

User experience

Localized UI (English, Russian, French): calendar and coverage, schedule configuration, request inbox, account settings, workspace permissions, provider publication status, and audit trail. Saving/approval succeeds independently of external availability. Publication failures and drift must remain visible.

Public holidays are collapsed by country by default, with the total count beside the country name. Human-readable dates use DD-MM-YYYY, including editable date fields; timestamps additionally show the user's local time and UTC offset. API/storage dates remain ISO. CSV imports accept ISO or DD-MM-YYYY dates.

Schedule removal shows a server-generated dependency summary before applying. Users can remove its escalation steps when every affected policy retains at least one step, or redirect schedule references to another existing schedule. Coverage fallbacks require redirection. Future applied replacements block removal; pending requests touching the removed schedule are cancelled as whole requests. History is retained. Removal, dependency changes, cancellation, audit, and publication are committed together against the reviewed revision.

Routing rules are evaluated from top to bottom. Drag-and-drop and keyboard/touch move buttons save the ordered list of rule IDs; the server requires an exact permutation and assigns consecutive priorities. The rule editor selects an insertion position instead of exposing conflicting numeric priorities. Reordering requires schedule-edit permission and rejects stale versions.

Identity is provider-independent: user IDs, memberships, sessions and audit identities are internal; an SSO identity binds (issuer, subject) to one user. Sessions last 12 hours; sign-in attempts are throttled per username. Demo mode must be explicitly enabled and uses only the local preview provider.

Daily use starts on the calendar with incoming requests and one-click Accept/Decline, outgoing pending requests, and the selected schedule's current/next personal shift. Schedules are searchable. Clicking a duty day prefills owner, schedule and exact local boundaries. Own days offer searchable teammates; other people's days preselect the signed-in employee. An exact incoming request can be accepted directly from that day. Split days explicitly choose a segment; past days are read-only. Start/end dates and clock times are always visible and are validated on the server before send. Reciprocal swaps are optional and have their own explicit start/end boundaries. Force permission adds a separate Apply without approval action.

Infrequent administration lives under Setup. Dialogs name their action. Project-to-people dictionaries use ordered person chips, name/email search and spreadsheet paste; simple lists use direct rows. The complete typed structure editor remains available for arbitrary nested data. Both editors share server validation and activation preview.

Disposable demonstration mode

Before migrations or deletion, startup checks a separate database provenance marker recording the last runtime mode. Only positive previous-demo provenance permits resetting application data. An unmarked database, a normal-mode database, or an unreadable marker is protected. Demo startup switches to a separately named database (or an explicitly supplied empty demo database), with a permanent prominent header and server-log warning requiring administrator attention. An empty database can be claimed only after proving it has no user relations. If isolation cannot be established, startup fails without changing protected data. No DROP DATABASE, schema deletion, or TRUNCATE CASCADE is used. Migration history and runtime metadata survive resets; another active database client blocks reset.

During a run, a minute-based maintenance task soft-deletes demonstration workspaces and their configuration graphs after 24 hours from creation, and accounts that an instance administrator created during the run after 48 hours. They disappear from access/listing immediately; dependent data is physically removed after a ten-minute grace period, preserving referential integrity. Nested schedules, contacts, variables, rules, absences, and memberships expire with their owning workspace rather than leaving broken references. Requests expire independently after two hours from creation; audit/history have six-hour retention with a historical boundary snapshot retained. Editing never extends creation-based lifetime. Times are configurable. The seeded accounts are pinned and remain usable; seeded workspaces are re-created from the demonstration configuration and reseeded after expiry. The permanent header describes retention and restart reset. Normal mode never runs demo retention.

The minimal examples are Acme Engineering / Project matrix (six services with two or three ordered owners during working hours and a shared weekly Monday 11:00 Time off rotation) and Customer Support / Daily support (daily 09:00 shifts and a 15-minute escalation). Demo mode uses the built-in configuration src/demo-config.yaml, which has the same format as a production file, and signs in only with passwords (no SSO, no registration). Every start generates one password for all demonstration accounts, writes it to the log and shows the account list with the password on the login screen. admin is the instance administrator (a bootstrap account); alice administers all four workspaces; bob, carol, dave and erin are employees. Bob, Dave and Erin demonstrate Engineering swaps; Carol participates in Engineering and demonstrates Support approvals. The Commerce example includes an email-only contact without login access.

Anonymized reference behavior

Alice, Bob, and Carol rotate weekly from Monday at 11:00. Payments primary is Alice and secondary is Bob. If Alice is absent on a duty day, Bob covers Payments; if both are absent, that day's effective general on-call employee covers it. Public holidays follow each person's country; when both are off, coverage falls back to general on-call. A confirmed replacement of Carol by Alice on the general rotation must also change any project coverage that falls back to that rotation.

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

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.

A user's display timezone defaults to the browser's IANA zone and can be overridden in account settings. It never enters scheduling state. Calendar intervals and activity timestamps show local time and UTC offset; configuration dates and time inputs explicitly use the workspace/country calendar. Profile country changes update that user's scheduling-country snapshots and publication requests atomically; display-timezone changes do not enqueue publication.

Holidays have a date, name, and ISO 3166-1 alpha-2 country. Coverage skips a primary or secondary only when that person's country has a holiday (or they are absent); then it falls back to general on-call. Ordinary rotations remain continuous 24/7, including holidays. Legacy global holidays remain explicitly marked “All countries” and keep their original fallback behavior. A legacy person's unset country defaults to Cyprus.

CSV imports use country,date,name headers, ISO or DD-MM-YYYY dates, and country codes such as AM and CY. Pasting previews the missing holidays against current server state without saving. Import is add-only and revision-checked: identical duplicates are skipped, different names for an existing country/date are reported and preserved, and invalid rows block the whole import. A stale preview must be refreshed.

JSM schedule participants use Atlassian account IDs. A manual account-ID mapping takes precedence; otherwise the provider resolves a person's email against the Jira user directory before any remote writes. Only a unique exact active match is accepted. Hidden, missing, or ambiguous addresses block publication with an actionable error. Meguri does not provision Atlassian accounts or configure their phone numbers/contact methods.

Localization and setup navigation

English, Russian and French are bundled in a single runtime catalog. The account language preference overrides the browser; an unset preference uses the first supported browser language, then English. Language affects presentation only, never schedule calculation or publication. User-entered values and third-party diagnostics remain opaque. All built-in text, including accessible labels and validation messages, must come from catalog entries.

Menu entries are real links carrying view and workspace. Reload, new tabs and Back/Forward restore the accessible workspace and section. Unknown sections fall back to Calendar; unauthorized workspace identifiers never grant access. Legacy schedules/policies/generation links open Schedule setup.

Schedule setup combines variables, generation rules, schedules, escalations and routing. Manual resources come first; generated resources follow a separator with a distinct style and source badge. Generated resources cannot be edited directly: their source button opens the owning rule. Routing badges retain actual evaluation priority even though resources are grouped by origin. Generation edits use ordinary inline values and direct variable/dictionary selections, with examples; custom loops, conditions and bindings remain available. Examples are illustrative; debounced server preview is authoritative before activation.

Uncovered future assignments show the employee, schedule and exact interval in the user's timezone with an action to arrange cover. Provider failure diagnostics include the first affected assignment. The UI lists at most 50 intervals and shows their total.

Cover requests expose start/end dates and clock times directly. Ask for cover defaults to one calendar day at the workspace working interval start, in its IANA zone. Clicking an existing duty segment preserves that exact segment; edits use explicit endpoints. Display timezone never changes input interpretation. Reciprocal legs have their own dates/times. The server resolves wall-clock endpoints and rejects ambiguous/nonexistent local times. Generation binding/person editors replace the long form with a focused subview and return to the originating field. Validation state and the first error stay visible in the sticky footer; all errors expand there.

Generated names using an iteration value/key display the source token and editable suffix together in a single bordered row; an existing prefix is retained and displayed in that row too. Binding semantics and generated identities remain unchanged.

Generation value editors show a reactive, read-only, formatted JSON example in a compact scrollable monospace textarea. Iterated values use the first available row with its context shown; missing values clear the preview. Invalid field types still display the selected raw data while server validation remains authoritative. Generated references show resource metadata explicitly labeled as an example, not the resolved identifier. Preview output is bounded to 20,000 characters.

Iteration preview context names object entries by their text name/title when available; unnamed list entries use a one-based position and dictionaries retain their keys.

Resource source pickers distinguish existing schedules/policies from generated schedules/escalations in their labels. A priority-owner or rotation schedule is also a valid fallback; fallback cycles remain prohibited by server validation.

The generic binding source is “From a generated resource”; contextual choices say “Generated schedule” or “Generated escalation”, contrasted with existing resources. A short hint explains resolution during generation and current-item scope.

Generation pickers offer direct references only to manual schedules and escalation policies, in both simple and advanced editors. Generated instances are reached through generator outputs, never duplicated as existing choices; new escalation-step defaults follow the same rule. Legacy saved direct references are retained, with an editor warning to select a generator source or manual resource rather than silently changing their scope.

Variable and iteration bindings share a monospace template notation throughout generation forms: { var.responsible[Project.value] }, { Project.key }, and one-based nested paths such as { var.people[1] }. Names requiring escaping use quoted bracket notation. Prefixes/suffixes remain outside braces. This is presentation only, not an editable expression language; binding data and server interpretation are unchanged.

Template notation remains visible in all form controls, but inputs, selects and buttons keep their standard styling. The monospace token background is reserved for non-interactive text blocks and template previews.

Returning from generation value/person editors restores the parent modal scroll after the main form is visible and laid out; focus restoration must not move the viewport.

Audit details resolve known person, schedule, escalation, routing, variable and rule IDs into name/id pairs. Names use workspace-scoped historical snapshots up to the event time, retaining last-known labels for removed resources; current directory/state fills gaps when history is unavailable. Unknown IDs and free-text comments remain unchanged. Display timestamps use the viewer timezone, with original immutable JSON and UTC timestamps available in a separate disclosure.

Variables expose one keyed collection type: Dictionary / object. Legacy record nodes normalize recursively to dictionary during deserialization, preserving node IDs, keys, values, nesting and generation references, including stored history. No separate record type appears in the editor.

Generated-resource references show both their resource type and name/template directly on binding buttons, including escalation recipients, without opening the editor.

Schedule setup begins with small collapsed absence/holiday widgets, followed by variables and generation rules and a compact expandable review/apply bar. There is no anchor menu or starter-example action; demo seeding supplies examples. One resource inventory groups manual resources first and generated resources after a divider. Schedules, escalation policies and routing rules use the same collapsed rows, with type/name in the summary, generated star and distinct color, and details/actions inside. Routing retains explicit evaluation positions and ordering controls. Three creation controls above the inventory create schedules (with a type chooser), policies and routes. Generated resources remain read-only with a source-rule action.

An approved replacement can itself be replaced from the calendar, using the current effective owner. Pending requests leave the existing cover active; approval or authorized force replaces only the selected interval. Original requests and audit history remain intact. Ownership and substitute availability are rechecked on application.

Rule summaries show iteration sources in template notation only when iterations exist, and count/type summaries for outputs. Collapsed resource rows include concise rotation/owner, escalation-step or route-condition details. Every generated resource has the same top-right source-rule link inside its expanded body, labeled with the rule name; viewers without edit permission see the attribution without an enabled edit action.

Schedule types are Rotation and First available. A permanent assignee uses a one-participant rotation. The generation block picker groups schedules, escalation policies and routing rules separately. The former fixed-assignee type is removed without compatibility or history conversion.

Data previews expand known person/resource references into compact named objects (with original identity retained), including nested lists and dictionaries. This is a readable display of references, not a conversion of stored values to objects.

Validation is a permanent status/details control in edit forms and generation setup. Live previews show checking/valid/error states; forms without a preview explicitly say not checked yet and validate on save. Diagnostic details are collapsed, with scoped source-rule/variable edit actions that retain the current local draft. Secondary generation errors are marked as possible consequences. Absence impact rows offer Choose a substitute: it adds an exact, schedule-specific interval and opens the person picker; other schedules and automatic project fallbacks remain unchanged.

Absence ranges use one editor for manual creation and impact-row shortcuts: schedule scope (one or all affected), start/end dates and times in the workspace zone, and substitute. Endpoints are half-open. Shortcut ranges remain fully editable. All-schedule ranges intersect the absence window of each schedule; explicit single-schedule ranges must fit its absence window. The absence itself retains inclusive duty-day dates.

Coverage diagnostics distinguish pending approval from missing substitutes. Pending request boundaries split uncovered intervals so partial requests never disguise remaining gaps. The UI shows substitutes and outstanding approvers, links to requests, and keeps publication blocked until approval. Browser regression covers partial pending coverage, remaining gaps, approver filtering, and responsive spacing.

Schedule setup places narrower variable and generation-rule panels beside a stacked availability sidebar. Absences and holidays remain open; their lists scroll internally. Country holiday groups retain accordions inside the bounded list, with add/import actions opening dialogs. Responsive layout moves the sidebar below the editors. Browser checks cover column position, bounded holiday expansion, visible actions and mobile width.

Generated routing placement is shown only when manual routes and an enabled route-generating rule coexist. Options name the manual routes in evaluation order; the stored insertion index is unchanged. Browser checks verify hidden controls for generated-only setups and named options/order for mixed routing.

The workspace-variable list scrolls within a 360px maximum height; its heading and add action remain outside the scroll area. The list supports keyboard scrolling and retains natural height when short.

Generation-rule summaries explicitly prefix rotation and priority output types with a localized Schedule label, distinguishing schedules from escalation policies and routing rules.

The generation-rule list uses the same bounded, keyboard-scrollable area as variables. Its add action and routing placement control remain outside the scrolling list.

Resource inventory is a distinct UI component (resource-inventory.html). All expanded resource kinds share resource-details.html: labeled icon rows, consistent spacing, sequential connectors, and a common source-rule control. Presentation rows and monochrome SVG paths come from pure UI helpers; schedules, routing order and provider state are unchanged. Check rotation, fixed owner, priority fallback, escalation timing/repeats and routing conditions on desktop/mobile.

Expanded resources use compact type-specific horizontal widgets instead of parameter tables: rotation metadata plus ordered people, priority people plus fallback, escalation steps plus repeat footer, routing conditions plus destination. Shared icon, chip and connector styles keep them consistent. Chains wrap within narrow screens; source links and edit actions retain their common placement.

The generation-rule editor is a generic technical constructor: visible iteration aliases and collection bindings, an explicit optional condition, and typed output blocks. New rules start without implicit project/responsible-variable detection or generated blocks. Variable names carry no semantic meaning. Compact controls retain the existing typed binding editor, value preview, live server diagnostics and draft semantics. Check no-iteration rules, nested collection bindings, conditions and multiple output types on desktop/mobile.

Binding selection uses one anchored, viewport-clamped popover beside the originating control; the constructor remains visible and keeps its scroll position. All source kinds, nested lookups/lists, paths, text affixes and live value previews remain supported. Literal escalation delays are directly editable. Done/Escape closes the popover and returns focus. Server and change previews are collapsed summaries with counts and bounded change lists. Browser checks cover context retention, source editing, nested bindings, keyboard dismissal and mobile bounds.

Output block names use one standard input and the shared ellipsis binding picker. Plain literal names are editable inline; bound names show their complete expression including affixes, with binding edits confined to the popup. Separate suffix/prefix fields are no longer shown in the constructor.

The rule editor materializes absent optional escalation repeat count/wait fields as literal zero in its draft, matching server defaults. Explicit bindings and invalid values remain unchanged for validation; opening/cancelling does not mutate saved rules.

Server-preview changes use labeled green/create, blue/update, red/remove and gray/unchanged badges. The expanded list scrolls at 320px (280px on mobile), with a compact footer toggle.

The main Acme Engineering demo contains six services (Payments, Search, Billing, Identity, Data platform, Developer portal), each with two or three ordered owners drawn from five login members: alice, bob, carol, dave, erin. All five share the weekly Monday 11:00 Time off rotation. Dave and Erin only access Acme; Carol also retains Support/Commerce access. Other demo workspaces remain separate. Access comes from groups defined in the built-in demo configuration, exactly as in production; the accounts share one password generated at every start.

Generation rule results use the actual authorized server expansion, including schedules, escalation policies and routes, with resolved names and compact settings. The panel is always present with explicit checking, invalid, disabled and empty states; it respects iteration filters and does not simulate generation in the browser. Preview data contains only scheduling resources and scoped people, never integration configuration.

Numeric, date and time controls use compact value-sized widths throughout forms. Generator literal fields keep their binding button adjacent; dynamic references retain space for descriptive names. Date controls include room for the DD-MM-YYYY value and calendar button.

Generation field labels occupy their own grid row; each value and its picker share the next row. Boolean inputs stay checkbox-sized beside their labels, including working-hours bindings.

The default Engineering example stores projects as one list of dictionaries: each entry has name and an ordered responsible person list. The Project iteration reads both fields directly from its current entry; no parallel responsible lookup variable is needed. The independent on_call list still defines the shared weekly rotation.

Variable editing uses a bounded, progressively disclosed tree: only the root starts expanded, object rows show names and collection summaries, and nested values expand in place. Values mode hides type/key controls; Structure mode exposes them without losing edits. Duplicate assigns fresh IDs recursively; ordering and live server validation remain intact. Check nested editing, duplication, mode switching and narrow viewports.

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. 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 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.