Init
Checks / test (push) Failing after 12m20s

This commit is contained in:
ab committed 2026-09-24 14:35:05 +03:00
commit a47c4a0398
96 files changed
+25721

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
# Architecture
See [workspace variables and visual matrix rules](proposals/workspace-templates.md) for design rationale; implemented behavior is specified in product.md.
A modular Rust application serves the web/API and runs a durable publication worker. The same executable can run either or both roles. PostgreSQL is the sole persistence and coordination service. Production targets GKE with Cloud SQL for PostgreSQL; no Redis or message broker is required.
## Persistence and concurrency
Users, sessions, workspaces, memberships, audit, history, provider checkpoints, and sync jobs are Cot ORM models. Bounded typed documents use serialized JSON in text columns with a PostgreSQL JSON validity constraint and optimistic revision. Cot 0.7 lacks JSON/UUID model fields, so UUIDs also use validated strings. Do not introduce another ORM for these types.
Cot 0.7 has no transaction API. `Store::commit` uses one parameterized data-modifying PostgreSQL CTE through Cot `Database::raw_with`: compare-and-swap checks revision and authorization, then writes membership, history, audit, and the durable sync request atomically. A constraint failure rolls the entire statement back. Ordinary reads, sessions, and checkpoints use Cot queries/models. Pending requests do not change effective coverage. Published state is separate from desired state.
The worker conditionally claims a five-minute lease token. Publication has a four-minute deadline and HTTP requests a twenty-second timeout; no document lock spans HTTP. Verified operations get checkpoints. Completion updates status and audit atomically, guarded by the lease token. Newer revisions remain queued when older runs succeed or fail. Jobs retry with bounded backoff; fifteen-minute reconciliation extends the horizon and detects drift. Crashed workers leave recoverable leases. This is not a distributed fencing protocol: read-back/conflict handling remains essential after process suspension or an uncertain remote timeout.
Run Cot migrations once in a deployment job, then start replicas with `MEGURI_SKIP_MIGRATIONS=true`. The initial migration creates frozen Cot models; the second adds scoped foreign keys, constraints, and indexes in one atomic PostgreSQL block. A JSM destination can have only one local workspace binding. Never edit applied migrations.
## Scheduling model
Rotation and priority-owner schedules are separate typed variants. Priority-owner schedules reference another schedule as fallback; the dependency graph must be acyclic. Evaluation splits at local handovers and override boundaries. Resolve a schedule's approved overrides before letting other schedules depend on it. Adjacent equivalent intervals merge; the result records the assignment reason.
The engine receives explicit state and time bounds. It must produce the same result regardless of database, network, or provider. Approved changes and dated revisions support historical inspection; future configuration changes must not rewrite past history.
`configuration` contains pure dependency-aware removal planning and route permutation operations. Services authorize, revalidate the plan against the submitted revision and explicit current time, then commit through the existing history/audit/outbox boundary. Providers still receive ordinary schedules, policies, and consecutively ordered routes.
`absence` computes pure, bounded multi-schedule replacement plans and before/after summaries. Range inputs use each schedule's own handover. Requests reference the absence that produced them, permitting approval to revalidate original ownership against the same document with that absence removed, while checking substitute availability against current state. Automatic coverage fallback remains an engine rule. Draft previews never write; absence and all generated requests commit as one state transition. Unavailable assignments are diagnostics, never eligible future publication periods, including single-participant rotations.
`generation` is a pure typed data/binding interpreter and materializer. `generation_service` validates one workspace revision and authorizes that workspace, then atomically persists its variables, generated resources, history, audit and sync request through the same commit boundary. Provider calls remain outside activation. Workspace documents retain exact variables, rule definitions, and resource ownership manifests so expansion is reproducible. Legacy migration 5 introduced variables; migration 8 consolidates them into the workspace document. No applied migration is changed.
## Identity and permissions
Session cookies are HttpOnly, SameSite=Lax, and Secure under HTTPS. Mutations require same-origin requests and a session CSRF token. Passwords use Argon2. Resource lookup always includes workspace context and server-side membership. External identity linking is a future adapter, never a replacement for internal user IDs.
Replacement preview is a read-only service using the same engine, revision, participant and permission checks as submission. Calendar actions use server-generated interval boundaries; inclusive range inputs are converted through schedule handovers on the server. Volunteering changes the initiator, not the approval requirements. Comments remain in the backward-compatible reason/force_reason fields but are optional action annotations. Current-shift clipping uses one explicit server timestamp per validation/application.
## Provider contract
The `Provider` trait consumes canonical `Publication` data and a checkpoint store. Plugins are Rust modules linked at build time, without an unstable dynamic-library ABI. New providers add a typed integration variant, capability validation, dispatcher constructor, module, and contract tests. No engine changes should be needed.
JSM publishes finite single-person rotations with exact timestamps (maximum 500 per schedule). Escalations resolve local IDs to remote IDs; routing priorities become consecutive indices. Deterministic ownership names allow recovery when a create succeeds but its local checkpoint is lost. Read-back checks managed fields, arrays, timestamps, and external overrides. Only checkpointed owned resources may be deleted. Workspace-specific environment credentials never enter documents or audit.
Reconciliation covers configuration, not user notification preferences, forwarding rules, or delivered notifications. JSM has no cross-resource transaction here: partially published revisions stay failed until retries complete. A remote edit between GET and PATCH cannot be fenced with the local lease. A dedicated-team acceptance test must compare actual provider timelines as well as CRUD.
JSM REST references (verified September 2026):
- https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-schedules/
- https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-schedule-rotations/
- https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-schedule-overrides/
- https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-escalations/
- https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/api-group-routing-rules/
Random recipients and escalation repeats require JSM Premium/Enterprise. Provider validation must not pretend a feature is available merely because it is representable in JSON.
JSM documents `repeat.waitInterval=0` as disabling repeats even when count is nonzero; the reference Terraform uses this combination. Enabling repeats requires a positive interval. A disabled local count maps to provider count 1 / interval 0 because the provider count minimum is 1. The reference's immediate step uses delay 0; verify it in live acceptance because the published schema lists a minimum of 1. Never silently change immediate notification to a one-minute delay.
## Scheduling metadata and import boundaries
WorkspaceDocument owns versioned scheduling people, country holidays, and per-schedule handovers. Contacts do not become authentication principals. Workspace-directory reads are workspace-scoped; identity IDs cannot be invented or linked through unverified email. CSV parsing and comparison are pure functions; preview is read-only, and import rechecks the workspace revision and commits history, audit, and outbox together. Working intervals, variables, schedules and access updates share the same workspace revision; working-interval changes atomically update its route snapshots.
Workspace creation atomically creates its document, owner membership, initial history, audit and sync request. There is no parent entity or hidden default team. Migration 8 replaces empty legacy tenant/team tables and refuses populated legacy databases; demo provenance checks run before migration.
Migration 3 upgrades old JSON snapshots without changing old handover boundaries or global-holiday meaning and introduces workspace working intervals. Migration 4 adds user country (CY) and an optional display timezone (null follows the browser). Current scheduling state copies the workspace timezone; historical documents retain the timezone effective at their revision. Country-profile changes propagate as dated workspace revisions, while display-timezone preferences remain outside the engine.
Email-only recipients resolve through [Jira user search](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-user-search/#api-rest-api-3-user-search-get) with an exact, active, unique email match. The authenticated service account needs Browse users and groups permission and appropriate Jira API scopes in addition to Operations access. Profile visibility may hide emails; explicit Atlassian account IDs remain the fallback. All required recipients resolve before the first provider write. Tests inject a localhost directory and never query real employee data.
## Demo runtime boundary
`demo_runtime` chooses the database before Cot migrations. A separate `public.meguri_runtime_guard` records last startup mode; proof and reset recheck it under a PostgreSQL advisory transaction lock. Empty-DB claiming checks user relations. Reset enumerates only Meguri application tables, uses no CASCADE, and preserves migration/provenance metadata. Distinct application names detect another active client. A protected configured DB triggers a separate database and an immutable process notice surfaced by `/api/session`; no credentials are exposed. Normal startup marks its database normal and never schedules cleanup.
Migration 6 adds nullable demo deletion timestamps and seed pins. Cleanup gates every write on demo provenance and uses explicit creation timestamps and a supplied clock. Workspaces expire as complete configuration aggregates, revisions invalidate in-flight edits, listings/access exclude expired resources, and a later CTE deletes dependent rows in one statement. Request expiry saves a new historical document and outbox job. History compaction retains a boundary snapshot. A minute timer refreshes expired seed workspaces through the ordinary services; only the five baseline account identities are permanent during a run. External provider writes are disabled in demo at both configuration and dispatch boundaries.
## Presentation catalog
Migration 7 adds nullable en/ru/fr user language. A LazyLock Rust catalog and eagerly loaded browser catalog share messages.json. Parameter substitution is single-pass text, never HTML. API diagnostics retain English compatibility and are localized at presentation. Account language updates use the profile write path without scheduling/outbox changes. Navigation uses browser history and ordinary links; stale workspace responses cannot replace the currently selected workspace.
Cover request date/time inputs are resolved through the engine's IANA wall-clock resolver on the server. Optional clocks preserve legacy schedule-handover API behavior when omitted. Preview and mutation share this normalization path and the same ownership/availability validation.
Audit reads enrich payloads from scoped history snapshots and current directory fallbacks without rewriting stored events. Workspace mutation audit timestamps reuse the effective timestamp returned by the history CTE, ensuring the event resolves against its own revision.
Applied requests retain original legs and optionally store effective_legs (None means original legs for legacy documents; an empty list means fully superseded). Application subtracts new spans from older active spans, preserving both sides of partial intervals. Engine, provider identities and configuration protections use effective spans; all updates share the existing revision-checked transaction.
Binding preview uses a presentation-only formatter registry for people, schedules, escalation policies and routing rules. A map built from the already authorized client state resolves known IDs/exact emails to shallow name/type/identity objects in a recursive walk. Unknown references and scalar values remain unchanged; generated-output metadata stays an example. No extra network queries or changes to binding values, generator types or saved JSON.
Generator issues carry optional rule/block/variable UUIDs for navigation, independent of localized paths. The client never publishes when navigating diagnostic links. Diagnostics always stay within the authorized workspace. CoverRange optionally contains exact UTC bounds and a schedule ID, validated inside the absence and declared duty days. Overlap checks compare effective intervals per schedule; exact ranges on distinct schedules may coexist. Existing bulk duty-day ranges keep their behavior.
CoverRange.local carries optional schedule scope plus local date/time bounds. The engine resolves both via the workspace IANA zone, rejecting DST gaps/folds and invalid bounds. Existing day-only and exact-UTC API ranges remain compatible; the UI emits local ranges exclusively.
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.
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.
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.
## Workspace aggregate
`WorkspaceRow` stores one `WorkspaceDocument` and one revision. The document owns working interval, drift policy, variables, rules, schedules, people, holidays, absences, requests and provider configuration. `WorkspaceMemberRow` stores per-user grants and ownership; there is no parent entity. History, audit, jobs and checkpoints reference one workspace ID. Generator activation and working-interval updates use the ordinary atomic workspace commit. JSM team IDs identify external provider destinations, not internal teams.
Variable list summaries render nested nodes by type. Object entries prefer a nonempty text `name` or `title`; otherwise they show a bounded key/count summary. People resolve through the scoped directory. Summaries never read a container’s null scalar slot as its content.
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.
Variable-tree indentation uses stylesheet depth classes, not inline custom properties (blocked by the application CSP). All row contents share the inset, with vertical guides for ancestor levels and smaller but preserved mobile steps.
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.
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.
+16
View File
@@ -0,0 +1,16 @@
# GKE and Cloud SQL
Target: GKE, Cloud SQL for PostgreSQL 17, and Cloud SQL Auth Proxy using Workload Identity. PostgreSQL is the durable queue and state store; no Redis is required. Templates are in `deploy/`. They have not been applied to a cloud project.
1. Provision a private-IP Cloud SQL instance, database and application login; enable backups/PITR. Use a VPC-native GKE cluster with access to the database VPC. Configure Workload Identity and grant its Google service account Cloud SQL Client. Follow [Google's GKE connection guide](https://cloud.google.com/sql/docs/postgres/connect-kubernetes-engine).
2. Build `Dockerfile`, push your image, and replace project, region, instance, release, service-account, image, and public URL placeholders. Review/pin the proxy image for your rollout; the template uses the version in Google's documented example. Native sidecars are required. Docker/GKE deployment must be validated in your environment.
3. Create the Kubernetes service account and ConfigMap from `gke.yaml`, then the `meguri-secrets` Secret through your deployment secret manager. It needs `DATABASE_URL=postgresql://USER:PERCENT_ENCODED_PASSWORD@127.0.0.1:5432/meguri`. Proxy-to-database transport is encrypted; loopback stays inside the pod. Add tenant-specific JSM `_EMAIL` / `_TOKEN` variables only for configured tenants. Never commit rendered secrets.
4. Apply only `migrate.yaml`, wait for its successful completion, then apply the Deployment, Service and PDB. Migrations use the same image with `MEGURI_ROLE=migrate`. Replicas have `MEGURI_SKIP_MIGRATIONS=true`; do not race migrations during rollout.
5. Add your internal HTTPS ingress. `MEGURI_PUBLIC_URL` must exactly match the browser origin (scheme, host, port); mutations and cookies depend on it. The default Service is ClusterIP. Keep access inside your organization while OIDC is being added. To bootstrap local accounts, temporarily enable signup in that restricted environment, create accounts, then disable signup. Never enable demo seeding outside loopback.
6. Run health checks, a two-user replacement, a tenant-isolation check, and the provider acceptance checklist in `testing.md`. Enable real JSM integration only after preview and acceptance.
The default Deployment has two replicas serving both web and workers. Conditional database leases serialize each team's publication. Roles can later be split into `web` and `worker` deployments without changing the binary. On interruption, a completed remote step remains checkpointed; an unfinished lease expires after five minutes.
Monitor failed/conflict sync jobs, oldest `last_success`, and `window_end` approaching the current time. Meguri publishes a finite horizon and must keep running to extend it. Configure operational monitoring around these values; Meguri does not send incident notifications itself. Readiness checks PostgreSQL; liveness checks the process.
The current polling worker and history/audit reads target team-scale workloads. Benchmark and add query pagination/queue batching before broad multi-tenant SaaS rollout. Configure ingress-level rate limits in addition to per-process login throttling. Password recovery, session administration, OIDC, history archival, and live provider acceptance are subsequent operational work.
+62
View File
@@ -0,0 +1,62 @@
# Development
Requirements: Rust 1.97 (Cot itself requires 1.94), PostgreSQL 17+, and a browser. Node is only needed for browser automation or upgrading vendored browser assets; serving the application has no Node build step.
Use `.env.example` as the list of supported settings. Environment files are not loaded automatically. Start a dedicated PostgreSQL database, set `DATABASE_URL`, then run `cargo run`. Database migrations run before the server accepts requests.
For an isolated Windows development database, use `scripts/dev.ps1`. It uses installed PostgreSQL binaries, creates data under ignored `target/`, binds the database and app to loopback, enables registration, and explicitly seeds demonstration accounts. `scripts/stop-dev.ps1` stops only the processes started for this workspace.
Required checks:
```text
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
```
PostgreSQL integration tests require `MEGURI_TEST_DATABASE_URL` pointing at a disposable database, with CREATEDB for the isolated demo-lifecycle tests. Run `cargo test --test integration -- --ignored --test-threads=1`. Most tests create isolated workspaces/users; lifecycle tests create and remove uniquely named databases to test protection, reset, retention, and reseeding. Never use a production database.
Browser checks: sign in; inspect the calendar; create a request as one user and approve it as another; verify the publication status separately; verify that a user outside the workspace cannot read it. Use both desktop and narrow viewports. A test failure is not a reason to relax an authorization or scheduling invariant.
## Operational limitations
For automated browser checks, run `npm ci`, `npx playwright install chromium`, then `npm run test:browser` against a seeded local app with signup enabled. By default the tests use installed Chrome; set `MEGURI_BROWSER_CHANNEL=chromium` for downloaded Chromium. The original suite creates its own workspace. `tests/browser-calendars.cjs` creates an isolated test account and checks email participants/absences, country defaults, reactive CSV import, workspace working hours, policy flows, and display-timezone isolation. `tests/browser-configuration.cjs` uses another isolated account to check removal dependencies/redirects, native routing drag/drop, saved positions, insertion/removal renumbering, and mobile controls. Screenshots go under ignored `test-results/`.
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.
Read [testing.md](testing.md) when changing rules and [deployment.md](deployment.md) before preparing GKE deployment.
## Demo lifecycle
`MEGURI_SEED_DEMO=true` enables disposable demo mode, not just seed insertion. It is limited to loopback and a single `all` or `web` process. Startup verifies the previous database mode before migrations, resets only positively marked demo application data, migrates, and seeds two examples. Normal startup records `normal`, including migration/worker roles. A missing or unreadable provenance marker never grants deletion authority. The existing development `meguri` DB will therefore be preserved on its first transition to this mode.
When the configured DB is protected, Meguri selects a separate `meguri_demo_<stable fingerprint>_<slot>` DB. The application login needs CREATEDB, or an administrator must supply an empty `MEGURI_DEMO_DATABASE_URL` with suitable permissions. Credentials are never shown in the browser/log notice. Protected fallback candidates are skipped, never dropped or overwritten. Isolation failure aborts startup. Do not manually mark a production database as demo. Another active client blocks reset; stop the old demo process before restarting.
The permanent header explains creation-based expiry and any protected-DB fallback. Defaults: workspaces 24 hours; visitor accounts 48 hours; requests 2 hours; audit/history 6 hours; physical purge 10 minutes after soft deletion. Configure seconds with the `MEGURI_DEMO_*` settings in `.env.example` (60 seconds–30 days). Sweeps run every minute. Nested configuration expires with its owning aggregate to preserve dependencies. Seed accounts remain; baseline workspaces are reseeded after expiry. No cleanup runs in normal mode. Demo rejects real provider integrations/publication.
Alice (`meguri-demo-only`) leads both examples. Bob belongs to Engineering; Carol belongs to Support. Engineering uses `projects`, `responsible`, and `on_call`; Support has independent `queues`, `agents`, and `lead` variables. All generated state is activated through the regular preview/commit code path with the mock provider.
Additional browser suites: `browser-absence.cjs` checks collapsed holidays, DD-MM-YYYY input, live bulk absence impact, range replacements and force; `browser-generation.cjs` checks demos, their permanent notice and isolated memberships, visual data/binding editing, early errors, preview without writes, stable generated IDs, and mobile forms. Restart after browser tests to return to the four clean examples.
OIDC is intentionally deferred; local password authentication is implemented first. No external JSM account is needed to run the mock provider. JSM writes require explicit integration configuration and workspace-scoped environment credentials. Contract tests use a fake HTTP server, never a production account.
Workspace configuration edits take effect when saved. Historical queries use saved configuration revisions. There is no user-configurable delayed activation of a whole configuration revision in the first release.
`tests/browser-cover.cjs` exercises daily volunteering/approval and optional comments in a separate disposable workspace, with desktop and mobile screenshots. Configuration navigation is grouped under Setup.
Navigation and language coverage: tests/browser-navigation.cjs checks direct menu/workspace links, reload and browser history, saved language versus browser preference, Russian/French display, safe interpolation, catalog completeness and the prohibition on literal template copy. It runs in npm run test:browser.
Demo workspaces import `src/demo-holidays-2026.csv` through the regular holiday service: 15 Cyprus dates supplied by the project owner and 18 Japanese holidays/days off from https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html (2026 table, verified 2026-09-24). This is a fixed 2026 example, not a recurring calendar or an annual-date extrapolation.
Demo workspaces: Acme Engineering (project priorities + weekly cover), Customer Support (daily queues), Regional Platform (dictionary regions → nested enabled services, region-specific handovers/rotation lengths, shared commander), Commerce Operations (product → tier dictionary lookup, variable escalation delays/repeats/working windows, shared weekly fallback). Alice owns all four; Bob is a member in Acme/Regional, Carol in Support/Commerce. Regional times use the workspace scheduling zone, not a per-region zone. Disabled Sandbox and Legacy export illustrate filtering; enabling the latter requires adding its missing retired tier or choosing an existing tier. All examples use normal generation preview/apply and demo cleanup; provider remains local preview.
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. Login password remains `meguri-demo-only`.
## Single workspace schema
Migration 8 replaces the former tenant/team tables with workspaces and workspace memberships. It requires an empty legacy schema; it refuses to erase a populated non-demo database. Demo startup resets only provenance-verified demo data before migrations and recreates four independent workspaces. No history conversion or runtime compatibility layer is provided. All settings and generation state share the workspace revision. API paths are `/api/workspaces/{workspace}`; navigation uses `?workspace=...`. PostgreSQL integration tests use the dedicated `meguri_workspace_test` database.
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.
Generation map uses locally vendored Cytoscape.js 3.33.1 (`static/vendor/cytoscape.js`, adjacent MIT license), with no CDN requests or Node build requirement. Its preset lane layout and inspector live in `static/generation-map.js` / `.html`; engine trace data is returned by the authorized generation-preview endpoint. Run `node tests/browser-generation-map.cjs` against the disposable demo for nested iterations, filtering, diagnostics, read-only behavior, canvas interactions and mobile layout.
+177
View File
@@ -0,0 +1,177 @@
# 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. Workspace grants separately control reading, schedule editing, replacement requests, forced application, membership administration, provider configuration, and audit access. Workspace owners administer their workspace and its drift policy.
## 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 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.
- 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.
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
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.
Local password authentication is the initial identity adapter. User IDs, memberships, sessions, and audit identities are provider-independent; later OIDC identities bind `(issuer, subject)` to an existing local user ID. 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 visitor workspaces and their configuration graphs after 24 hours from creation, and visitor accounts 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. Demo accounts remain usable; seeded workspaces refresh 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). Alice leads both, 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
Scheduling people are separate from login accounts. A workspace can select its members 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; an email is never proof of identity. Only authenticated, authorized users can approve requests; an email-only recipient requires a lead's explicit forced application until a verified account-link workflow exists. 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.
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. Login password remains `meguri-demo-only`.
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. 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.
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.
+86
View File
@@ -0,0 +1,86 @@
# Proposal: workspace data and schedule templates
This is a design-rationale document. Current behavior is defined in product.md and architecture.md: workspace is the sole scope, with no nested teams or cross-workspace bindings. Historical sketches below are not an implementation contract.
Status: the first implementation is available. See `product.md` for the maintained contract. This document preserves the broader design rationale; reusable parameterized template libraries, derived variables, AND/OR predicate groups, and separate row-level grants remain future work.
Implemented: arbitrary typed trees, stable list/dictionary entry IDs, up to four nested iterations, one optional predicate per rule, constant/variable/item/key/lookup/output bindings, text prefixes/suffixes, ordered priority coverage and other schedule outputs, policies, routing, debounced server validation, impact preview, and atomic activation of variables and generated configuration within one workspace. Edit/activation uses EditSchedules permission in that workspace. Singleton outputs may be shared between rules; iterated outputs stay scoped to their row. Limits are 200 schedules, 200 policies, and 500 routes; a 30-project matrix is covered by regression tests.
## Intended workflow
A workspace member with the appropriate grants creates their own named variables and builds reusable rules from visual blocks. Neither variable names nor record fields are fixed to projects/primary/secondary. For example, one person can define a shared list of on-call emails, a team-lead value, and a dictionary mapping each project to a different ordered list of responsible people. Another team can model regions, services, or shifts with different fields. The product supplies types and operations, not a mandatory project schema.
In the UI there are three views: **Variables**, **Rules**, and **Preview**. Lists are editable rows; dictionaries and records are tables/forms with user-defined columns. Rules contain “For each”, “If”, and “Create …” blocks. Each field offers “Fixed value” or “Use variable”; the picker shows compatible variables and fields of enclosing loop items. A breadcrumb such as “Current project → Responsible people” replaces an expression to type. List selection can choose the whole list, first person, or person at a specified position; dictionary selection can look up a key from another variable/current item. Missing positions/keys require an explicit fallback or produce a row-specific validation error. A rule can be saved as a reusable template, but an administrator does not have to prebuild every template for users.
Changing an input automatically regenerates the draft preview, showing resource counts and errors beside the originating row/block. Applying activates one consistent revision and queues publication. Automatic draft calculation must not publish an unreviewed destructive edit. No Rust, JavaScript, Terraform, or textual expression language is required.
### Separate project list and responsibility dictionary
The user's concrete input model is two independent variables, represented here as data examples, not a configuration language the UI requires:
```json
{
"projects": ["A", "B", "C"],
"responsible": {
"A": ["alice@example.com", "bob@example.com"],
"B": ["carol@example.com", "alice@example.com"],
"C": ["bob@example.com", "carol@example.com"]
}
}
```
The visual rule is **For each item in projects → Create rotation**. Its name binds to the current project; its participants bind to **Variable responsible → Entry with key: current project → Entire list**. This is semantically `responsible[current_project]`, built by selectors rather than typed syntax. Anchor, shift length, and handover can be fixed values or additional bindings. One schedule block creates A, B, and C rotations with different participant sequences; list order is the rotation order, not an escalation order. Extra escalation/routing blocks are optional.
This lookup joins collections by their values, never their row positions. A reordered responsibility dictionary has no effect. Duplicate project values, a missing dictionary key, an empty participant list, repeated participants, or an invalid email fail preview with the project and binding identified; there is no implicit fallback to another project. Unused responsibility entries may be shown as unused but create no schedules. A deliberate “Use default when key is absent” binding is possible; it must be visible in the rule. The entire activation fails on invalid rows rather than publishing a partial matrix.
Generated schedule identity follows the stable entry ID in the iterated project list and the create block ID. Editing/reordering the participant list changes future base ownership without recreating that schedule; it never resets the stored rotation anchor or drops applied replacements. Renaming a project keeps its hidden entry ID, but the user must update the dictionary lookup key as part of the same valid draft. Tests must distinguish reordering projects, reordering dictionary entries, and reordering a project's participants: only the last changes that rotation's ownership sequence.
Another supported layout is a single table of records. It is optional: users never have to merge the separate variables above into this format.
| Stable key | Project name | Primary | Secondary | Handover | Target team |
|---|---|---|---|---|---|
| first | First | Alice | Bob | 11:00 | Platform |
| second | Second | Carol | Alice | 09:00 | Platform |
The column types refer to real people, teams, times, and other resources. A dropdown selects references; a free-form name is not an identity. A useful starter type is “Person”: an existing workspace user or an email contact without login access. Plain strings and lists of strings are still allowed. When used as recipients, email strings need an explicit validated conversion to people; non-email strings are not guessed to be usernames. The team can edit allowed cells and add/remove rows without editing the rule structure.
For the minimal dictionary example, a single block can iterate `projects`, where each entry is a project key and its ordered list of people. Inside it, “Create rotation” takes its title from the current key and its participants from the current value; a second “Create rotation” takes the shared team lead as its only participant; “Create escalation” refers to these two outputs; “Create routing rule” matches the current project and points at that escalation. Thirty dictionary entries then produce thirty connected groups from the same four create blocks. If each project needs separate primary/secondary participant lists, change the entry to a record with those fields and bind two rotation blocks to them. Both are data models the user can choose.
Shared resources can be created outside a loop and referenced inside it, for example one single-participant rotation for the team lead instead of thirty identical rotations. Multiple rules consuming the same dictionary keep independent instance/output identities and explicit precedence; a lookup alone does not create a resource.
“Create alert” is deliberately not an action here: the generated routing/escalation configuration tells the external provider how to process alerts. Meguri continues to own configuration, not incident delivery.
## Four boundaries
1. **Workspace data.** Versioned, typed values: string, boolean, number, date, local time, duration, working interval, person/team/schedule reference, list, record, and dictionary. Time-of-day, duration, and timestamp are distinct types. A local time needs an explicit workspace/interval IANA zone before it can identify a schedule boundary; user display timezone is never an input. “Dictionary” means a declared schema or `map<string, T>`, not arbitrary unvalidated JSON. Secrets remain in deployment secret storage.
2. **Rules, templates, and bindings.** Persist a versioned, typed block tree with stable block IDs: `ForEach(collection, item)`, `If(predicate)`, and `Create(kind, fields)`. Value nodes are constants, variable references, loop-item/record fields, dictionary lookup, explicit default, name formatting, or references to a create block's output. A picker creates these nodes; users do not write the internal representation. Initial predicates can be equality, contains, and empty/not-empty with AND/OR. Each create block is a stable output slot (schedule, escalation, route). References use IDs and loop keys rather than interpolated display names. Reusable templates parameterize these blocks; an instance binds values and allowed target teams. Renaming a variable does not break references.
3. **Pure expansion and validation.** Resolve source variables and any explicitly derived values as an acyclic dependency graph, expand bounded loops into ordinary domain objects, resolve output references, then run existing schedule/dependency/provider-capability validators. Nested collection iterations may use outer fields; a Cartesian product is an explicit operation with a visible output count. Scope checking prevents a reference to an item outside its loop; referencing a conditional output requires compatible guards or an explicit fallback. No arbitrary code, eval, recursive templates, network calls, clock-dependent expressions, or unbounded loops. Reject cycles, invalid types, duplicate keys, missing identities, illegal grants, excessive nesting, and output limits before activation. Freedom to compose variables and blocks does not require a general-purpose programming language.
4. **Activation and publication.** Materialize the candidate team documents and a diff of create/update/remove operations. Show affected teams and dependent policies, and preserve an expansion manifest. After review, compare all input revisions and atomically persist the source revision, affected team snapshots, history, audit, and outbox jobs. External providers receive the same canonical publication contract as today. External writes can still partially fail; report their status separately from activation.
## Source of truth and stable identity
The source is the active combination of variable revisions, template versions, instance bindings, manual resources, and approved replacements. Generated team documents are immutable derived snapshots, never an independently editable second source of truth. Keep the exact inputs/manifest for reproducing a past expansion without loading today's variables.
Generated identity is based on `(instance UUID, stable loop-entry ID path, output block ID)`, for example via namespaced UUIDs. Renaming a project, variable, dictionary key, or reordering the table must not recreate schedules, lose provider mappings, reset a rotation anchor, or orphan replacement history. Every output records its source instance, entry path, block, and version. Lists and dictionaries have hidden persistent entry IDs separate from labels, keys, and ordinal positions. Dictionary keys remain unique for lookup; changing a referenced lookup key requires validation, while generated identity stays stable. Copying a block/entry deliberately creates a new ID. Rotation anchors are explicit stored inputs or creation-time values preserved in the manifest, never recomputed from today's date.
Generated objects show “Managed by template … / project …” and link to their source row. Direct edits redirect to the matrix or template. Permit only explicitly declared per-row parameters. If later needed, “Detach” copies the effective configuration into manual ownership with a recorded identity/ownership transition; it is not a silent local patch that the next expansion overwrites.
## Deletions, replacements, and rule order
Removing a row previews every removed output and its inbound references. References from surviving/manual resources require an explicit replacement or edit; never silently remove unrelated policies. Current/future approved replacements block destructive changes to their schedule. Pending swaps are cancelled only as an explicit part of the reviewed activation. Historical snapshots remain intact. Changing participants or anchors warns about changed future owners and preserves applied overrides using existing engine rules.
Order is semantic because routing uses first match. A template instance owns an ordered block of generated rules, with declared order within each row and a stable order of rows. Drag the block among manual rules; edit row/template order at its source. Do not derive priority from dictionary iteration, UUID ordering, or names. Cross-instance precedence is an explicit workspace/team setting.
## Permissions and multi-team changes
Separate rights to create/edit variables and their schema, edit data rows, build rules/templates, and activate changes. These grants can be delegated to team members, not only workspace administrators. Shared templates can restrict which columns/rows and target teams editors manage. A row cannot name an arbitrary tenant or team. Scheduling a person still does not grant them access. Workspace-wide data is intentionally readable to its workspace audience; never place team-private data in a globally readable variable.
Editing shared inputs produces a draft. Activation requires authority over every affected team, or approvals from those teams before one activation; do not partly activate an input revision. The diff lists the complete impact even when one row looks harmless. Source and team revision checks reject stale approvals. Keep activation local and bounded; no database write lock spans provider HTTP calls.
## Small first version
Start with user-defined primitive variables, people/email lists, records and dictionaries; constant/variable/current-item bindings; one `for each` collection; schedule + escalation + route create blocks; stable identities; preview/diff; and atomic activation for one target team. A starter “Project on-call” template is a convenience, not a special hard-coded model. This first version already handles different responsible lists per project plus shared team-lead/default values. Add conditional/nested iterations and reusable parameterized templates next, then explicit Cartesian products and multi-team activation. Reuse the existing engine, provider contract, and outbox. Do not start by prescribing a fixed table or introducing a programming language.
Before rollout, size domain quotas for this use case: the current limit is 50 schedules per team, so 30 projects with two schedules each already exceeds it. Establish tested resource/expansion budgets and provider-specific limits, show counts before activation, and reject excess atomically. Raising a cap alone is insufficient without measuring engine, history size, and publication cost.
Required tests: deterministic expansion; stable identity across rename/reorder; matrix limits and key collisions; type/reference/cycle validation; scope enforcement; deleting rows with inbound references or approved replacements; stale previews and concurrent activation; history reproducibility; no changes to unrelated manual resources; routing precedence; retrying publication of the same expansion.
+118
View File
@@ -0,0 +1,118 @@
# Test architecture
The daily cover workflow has a PostgreSQL regression for read-only preview, volunteering, owner approval, denied self-approval/third-party transfers, force permissions, empty comments and current-interval clipping. `tests/browser-cover.cjs` creates its own workspace and checks calendar → offer → owner acceptance, own-day request → exact-request acceptance, the privileged third action without a comment, early range errors, comment visibility, display timezone and mobile layouts. Browser navigation opens Setup explicitly; daily flows do not need it.
Scheduling errors are product failures. A change to ownership, time boundaries, approvals, access, or synchronization must include a regression at the boundary that could fail. Test observable outcomes rather than repeating an implementation formula.
| Layer | Location | Dependencies | Contract |
|---|---|---|---|
| Pure engine | `src/engine.rs` tests | None | Owners and intervals, DST, fallback, pending/applied overrides, validation |
| Configuration graph | `src/configuration.rs` tests | None | Dependency removal/redirect, last-step and fallback protection, pending swap cancellation, approved-history retention, exact route permutation |
| CSV import | `src/holidays.rs` tests | None | Country/date keys, quoting, BOM, duplicates, conflicts, invalid rows |
| Provider compilation | `src/providers/` tests | None | Unsupported capabilities fail; normalization detects changed ownership |
| Application/persistence | `tests/integration.rs`, `tests/support/` | Disposable PostgreSQL | Real Cot migrations, scoped authorization, concurrent edits, atomic audit/history/outbox, approval revalidation |
| HTTP provider contract | Integration suite + stateful local fake | PostgreSQL + loopback | Wire methods/payloads, partial 429 failure, checkpoint recovery, read-back, drift, external overrides, owned-only deletion |
| Browser/API | `tests/browser.cjs`, `tests/browser-calendars.cjs`, `tests/browser-configuration.cjs` | Seeded local app + Chromium | Forms, calendar, approval, workspace isolation, CSRF/origin, people/imports/timezones, removal dependencies, native routing drag/drop and position editing, desktop/mobile |
| Live acceptance | Dedicated JSM test team | Test credentials | Actual provider timeline, subscription-specific escalations and routing; never ordinary CI |
## Invariants
- Every schedule covers the requested range without gaps or overlaps. Tests exercise a year across five zones and five shift lengths.
- Spring and autumn weekly shifts can be 167 or 169 hours while handing over at 11:00 local. Exact boundaries belong to the incoming employee. Invalid local handovers fail.
- Overrides change only their interval, not the base sequence. Pending requests have no effect. Overlapping approved state is an error.
- Coverage uses secondary, then the effective fallback including overrides. Country holidays skip only affected people; legacy global holidays select fallback directly. Cycles fail validation.
- Swaps apply both legs or neither. Requesters cannot approve for someone else. Application rechecks availability and ownership. Force requires a grant and is audited; comments are optional.
- One of two edits with the same revision wins. A failed constraint leaves document, revision, history, audit, and job unchanged.
- Schedule removal rewrites references, cancels affected pending requests in full, and commits one history/audit/outbox change. It cannot erase a current/future approved replacement or leave an empty escalation or broken fallback.
- Route ordering requires every current ID exactly once. Missing, repeated, foreign, stale, or unauthorized requests change nothing. Changing priority preserves conditions/recipients and survives reloads.
- Publication snapshots cannot read newer revisions. Past absence flags do not block a now-correct future plan.
- Competing workers cannot claim the same unexpired lease. Retrying a lost checkpoint does not create duplicate remote resources.
- External changes and overrides block writes/deletion by default; unmanaged resources survive reconciliation.
## Running and extending
`cargo test` runs pure tests and explicitly reports database tests as ignored. CI also runs `cargo test --test integration -- --ignored --test-threads=1` with PostgreSQL 17. Fixtures use UUID names and never truncate shared tables. Use a disposable database. Run this suite serially because worker tests inspect the global queue. Worker tests explicitly prioritize only their own job so a bounded batch cannot skip it because of jobs left by a prior run.
The fake models JSM responses and is not a substitute for live acceptance. New endpoints need verification against official documentation, a wire assertion, and a failure/recovery test. Do not make the fake accept an invented contract to get tests green.
Before enabling a provider in production: publish a preview to its dedicated test team, compare the actual timeline, repeat unchanged, interrupt a partial publication, introduce external drift, verify conflict and explicit overwrite, and exercise escalation/routing on the target subscription. Record evidence and limitations.
## Additional regression cases
Cover independent handovers and dependent fallback boundaries, DST rejection, country-specific coverage, scoped directory selection, email contacts without access, import quoting/BOM/duplicates/errors/stale previews, atomic working-interval propagation, and exact JSM email resolution before writes. Browser coverage includes person selection by email, absence selection, reactive CSV preview/import, and readable escalation/routing flows on desktop and mobile.
## Variables, absence planning, and demo lifecycle
`src/absence.rs` tests range splitting across independent handovers, automatic project promotion, uncovered diagnostics, preserved owners, and replacement validation. `tests/generation.rs` checks deterministic 30-project expansion, stable IDs on rename/reorder, typed dictionary errors, loop scope, fallback cycles, protected applied replacements, workspace-specific IDs, three-person priority, country holidays, and a Monday rotation crossing autumn DST.
The PostgreSQL suite checks read-only previews, grouped absence approvals, atomic workspace-variable activation, permission isolation, stale reviews, concurrent activation, country snapshots, and no contact-to-login grants. Demo tests use uniquely named disposable databases with CREATEDB: unmarked/normal protection, safe isolation, application-only reset with unrelated sentinel data preserved, repeated migration/seed, creation-based soft deletion, later physical purge, and reseeding. Never repoint these tests at a production DB.
The two additional browser suites (`browser-absence.cjs`, `browser-generation.cjs`) exercise actual forms and early server errors, persistent/reloaded state, the permanent demo banner and login guide, generated source editing, country accordions, DD-MM-YYYY, bulk replacements, and desktop/mobile layouts. Inspect screenshots rather than trusting DOM assertions alone.
Localization/navigation regressions check supported browser fallback, persistent account preference, parameter safety, complete catalogs, no literal template text, direct links, reload, workspace changes and browser history. Generation browser checks retain server errors, atomic activation and stable generated IDs while exercising inline controls and the source-owner link.
Exact-time cover regressions exercise same-day partial cover, workspace clock defaults despite a different display zone, server ownership checks, malformed clock values, DST folds/gaps and 23/25-hour calendar days. Generator browser tests assert that binding edits replace the main form and live validation remains inside the viewport even at the top of a long form.
Legacy compatibility regressions deserialize two-person schedules and dynamic generation bindings into priority-owner resources while preserving participant order, resource identity, fallback, handover and routing references.
`tests/browser-binding-preview.cjs` checks reactive formatted binding values beside server type errors, clearing missing selections, first-row lookup context and mobile sizing without saving draft changes.
`browser-generation-scroll.cjs` checks scroll restoration from an editor near the bottom of a long rule on desktop/mobile and verifies template styling is limited to non-interactive blocks.
`audit` unit tests preserve comments and unknown IDs while resolving references. PostgreSQL regression checks historical names after rename, raw UUID preservation and cross-workspace lookup isolation.
`browser-setup-layout.cjs` checks compact review state, country holiday counts, collapsed widgets, import access and mobile width. Demo holiday imports also run through integration seed/reset checks.
Repeated-cover regressions check approval gating, partial interval splitting, preserved original legs, stale-owner rejection, forced corrections and calendar interaction on an already replaced day.
The setup inventory browser checks cover collapsed resource rows, the schedule-type creation chooser, top utility widgets, desktop/mobile sizing, and absence of the old anchor/example controls. Configuration browser checks expand manual rows before deletion/reordering and retain drag-and-drop routing coverage.
Advanced demo regression checks dependent iteration filtering, inherited regional handovers/participants, tier-based escalation settings and working windows, shared tier edits with stable resource IDs, and early validation of a newly enabled entry with a missing tier. Integration seed/reset verifies all four workspaces.
`browser-advanced-demos.cjs` opens both advanced generation editors, verifies clean server previews, and checks the demo workspace access matrix for Alice, Bob and Carol.
Setup layout checks also verify iteration-source summaries, omission of zero iterations, collapsed resource descriptions and source-rule navigation for all three resource kinds.
Diagnostics regression covers bad participant data, primary/secondary errors, scoped source links, draft-preserving navigation, always-visible status and recovery to valid. Absence tests cover exact interval selection, independent schedule ranges, rejection of overlap/out-of-absence bounds, and unchanged bulk range behavior.
`browser-diagnostics.cjs` exercises invalid participant data, rule/variable links with local draft retention, and recovery to a valid status on mobile. `browser-absence.cjs` additionally selects an uncovered impact row and checks exact schedule/time bounds before the existing bulk workflow. PostgreSQL tests verify exact absence coverage remains pending until approval and leaves surrounding hours uncovered.
Unified absence editor tests change the start time of a shortcut-created range, verify resulting server boundaries, and use the same date/time controls for manual ranges. Unit coverage includes local range scope, half-open bounds and DST rejection.
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.
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.
`browser-repeat-defaults.cjs` verifies absent escalation defaults render as editable numbers, edited values survive reopening the draft, and explicit dictionary lookups remain intact.
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. Login password remains `meguri-demo-only`.
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.
Permanent assignees are tested as single-participant rotations, including absence, replacement approval and publication blocking. The removed fixed-assignee schedule and generation types are rejected rather than normalized.
Workspace isolation regressions exercise independent memberships and variables, a single revision for configuration and working-time changes, read-only generation preview, stale review rejection, atomic activation/history/outbox, and owner settings permissions.
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. 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.
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.