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

+61 -1
View File
@@ -22,6 +22,12 @@ Browser checks: sign in with a demo account; inspect the calendar; create a requ
## Operational limitations
When configuring JSM, copy the entire team identifier between `/teams/` and `/on-call` in the Operations URL, including `og-` when present. Cloud ID and Meguri workspace ID are separate UUIDs. Operations rejects collection page sizes above 50; directory search has its own pagination contract.
Provider display names use the resource name followed by a stable ownership marker. Do not remove the marker manually; it supports recovery after a lost checkpoint. Existing technical names migrate on a successful publication, subject to the ordinary drift policy.
An exact-time JSM schedule can show a base rotation containing the current assignee (or `noone` before coverage begins) and generated future overrides in the provider UI. These are the exported effective assignments, not additional approval requests. A previously failed native-rotation write with no checkpoint is not automatically adopted: inspect it and explicitly overwrite the owned test schedule when repairing it. A successful remote create alone is never a synchronized publication.
For automated browser checks, run `npm ci`, `npx playwright install chromium`, then `npm run test:browser` against a freshly started demo (`MEGURI_SEED_DEMO=true`). The suites read the generated password from `/api/session` (`tests/support/browser.cjs`), so they need no configuration; set `MEGURI_BROWSER_URL` for a non-default address. By default the tests use installed Chrome; set `MEGURI_BROWSER_CHANNEL=chromium` for downloaded Chromium or `MEGURI_BROWSER_EXECUTABLE` for a specific binary. Workspaces cannot be created from the UI or API, so suites that need an isolated workspace create a fresh password user through the instance administrator API with groups from the demonstration configuration. `tests/browser-access.cjs` covers the login guide, absence of registration, the SSO error banner, the instance page and its read-only rules, employee permission editing (including force without approval), employee and outsider views, and workspace isolation. `tests/browser-calendars.cjs` checks email participants/absences, country defaults, reactive CSV import, workspace working hours, policy flows, and display-timezone isolation. `tests/browser-configuration.cjs` checks removal dependencies/redirects, native routing drag/drop, saved positions, insertion/removal renumbering, and mobile controls. Screenshots go under ignored `test-results/`. The suites modify demo data (some empty a demo workspace); restart the demo before repeating a run.
Windows development needs complete PostgreSQL server binaries and their `share/` data files, not a client-only installation. Pass `-PostgresBin` to use a portable distribution. The loopback-only local database uses trust authentication and is not a production configuration. The script serves a copy of the executable so Cargo can rebuild while it runs.
@@ -65,4 +71,58 @@ Generation map uses locally vendored Cytoscape.js 3.33.1 (`static/vendor/cytosca
## Browser-check sandboxes
Browser suites that change data cannot reuse the example workspaces: approved replacements are permanent history, so a schedule that has one can never be removed. Start the demo with `MEGURI_DEMO_SANDBOXES=12` to add empty `Sandbox 1..12` workspaces, each with its own administrator group `sandbox-N-leads` and employee group `sandbox-N-staff`. Every mutating suite creates a throwaway password user in its own sandbox through the instance-administrator API. Restart the demo before each full run; sandboxes are not reset.
Browser suites that change data cannot reuse the example workspaces: approved replacements are permanent history, so a schedule that has one can never be removed. Start the demo with `MEGURI_DEMO_SANDBOXES=14` to add empty `Sandbox 1..14` workspaces, each with its own administrator group `sandbox-N-leads` and employee group `sandbox-N-staff`. Every mutating suite creates a throwaway password user in its own sandbox through the instance-administrator API. Restart the demo before each full run; sandboxes are not reset.
Deleting an exported resource in JSM does not remove the desired configuration in Meguri: the next publication recreates it and its managed coverage, without requiring overwrite. To remove it permanently, remove it in Meguri. Existing external edits still obey the drift policy.
## 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.
To exercise random team escalation, select A random team member in a generator or import `{"after":30,"team":"random"}` in meguri/v2. No person binding is required. Use the local fake provider for verification; notification selection belongs to the external provider.
Email-only contacts do not approve replacements. Test ordinary requests, swaps and absence groups with account/contact combinations, including immediate application with no approvers and retained approval for real members. OIDC/email account linking remains deferred; never infer membership from an email match.