From e37d583179ff21671062e8139bc6dd7ca5ed94d0 Mon Sep 17 00:00:00 2001 From: AB Date: Thu, 24 Sep 2026 16:24:50 +0300 Subject: [PATCH] Fixed CI --- docs/jsm-acceptance.md | 32 +++++++++++++++++++++++++++++++ docs/product.md | 2 ++ docs/testing.md | 2 ++ static/generation.js | 2 +- tests/browser-binding-preview.cjs | 13 ++++++++++++- 5 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 docs/jsm-acceptance.md diff --git a/docs/jsm-acceptance.md b/docs/jsm-acceptance.md new file mode 100644 index 0000000..252c75e --- /dev/null +++ b/docs/jsm-acceptance.md @@ -0,0 +1,32 @@ +# JSM test environment and acceptance preparation + +Research date: 2026-09-24. No external tenant was provisioned or contacted with credentials during this research. + +## Environment + +No separate public mock/test Operations API host was found in the official reference. Use a dedicated test site's cloud ID with the ordinary Operations API. Basic authentication examples use `https://api.atlassian.com/jsm/ops/api/{cloudId}/v1`; OAuth 3LO uses a different gateway path. The existing adapter uses email/API-token basic authentication. Validate token type, scopes and both Operations and Jira directory access on the chosen site before writes. See the [Operations API introduction](https://developer.atlassian.com/cloud/jira/service-desk-ops/rest/v2/intro/). + +Atlassian documents [Forge demo development sites](https://developer.atlassian.com/platform/forge/provision-a-demo-development-site/) with Enterprise editions including JSM, one active site per developer and a default 90-day lifetime. Provisioning does not require installing an app first: + +```sh +npm install -g @forge/cli@latest +forge login +forge site provision +``` + +These sites are intended for development/testing; availability can be limited. Verify that Operations can be enabled and the required test people can be added before adopting one for acceptance. Meguri remains an external Rust service, not a Forge application. A separately provisioned JSM test site is the fallback. + +Do not assume an organization's sandbox copies its on-call configuration: Atlassian lists Operations among [data not copied to sandboxes](https://support.atlassian.com/organization-administration/docs/what-data-is-copied-to-sandboxes/). This does not by itself establish that Operations cannot be configured there manually. + +## Existing implementation and remaining evidence + +`src/providers/jsm.rs` already implements schedule, escalation and routing publication, identity resolution, checkpoint recovery and read-back. The integration suite exercises it against a local fake HTTP server. These are implementation checks, not proof of compatibility with the live service. + +1. Create a dedicated Operations team and two or three test users. Record site/cloud ID, team ID, account IDs and subscription capabilities. Keep credentials in deployment secrets. +2. Start a separate non-demo Meguri instance with a disposable database; application demo mode deliberately rejects real provider writes. +3. Verify read access to schedules and exact identity resolution. A person's email is a lookup key, not proof that JSM can notify them; their Atlassian account and notification settings must exist. +4. Publish a small weekly schedule and a partial replacement. Read the provider's final timeline and compare exact half-open assignments, including a DST boundary, against Meguri's publication. +5. Exercise immediate escalation, repeat settings, recipient variants and routing order on the actual subscription. Existing documentation/schema discrepancies (notably delay zero) remain acceptance gates, not permission to change scheduling semantics silently. +6. Repeat unchanged, simulate partial publication recovery, change a managed remote resource, verify drift blocking and explicit overwrite, then remove only resources created by this test. + +Record redacted requests/responses, API dates, plan and observed limits. Keep ordinary CI on the local fake; live acceptance must be explicit and restricted to the dedicated test destination. See [testing.md](testing.md) for the provider acceptance contract. diff --git a/docs/product.md b/docs/product.md index 963cbf8..4bbecf8 100644 --- a/docs/product.md +++ b/docs/product.md @@ -96,6 +96,8 @@ Generated names using an iteration value/key display the source token and editab Generation value editors show a reactive, read-only, formatted JSON example in a compact scrollable monospace textarea. Iterated values use the first available row with its context shown; missing values clear the preview. Invalid field types still display the selected raw data while server validation remains authoritative. Generated references show resource metadata explicitly labeled as an example, not the resolved identifier. Preview output is bounded to 20,000 characters. +Iteration preview context names object entries by their text name/title when available; unnamed list entries use a one-based position and dictionaries retain their keys. + Resource source pickers distinguish existing schedules/policies from generated schedules/escalations in their labels. A priority-owner or rotation schedule is also a valid fallback; fallback cycles remain prohibited by server validation. The generic binding source is “From a generated resource”; contextual choices say “Generated schedule” or “Generated escalation”, contrasted with existing resources. A short hint explains resolution during generation and current-item scope. diff --git a/docs/testing.md b/docs/testing.md index 760d2e8..6120744 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -39,6 +39,8 @@ CI database preflight connects from the same job container and with the same URL All browser suites launch through `tests/support/browser.cjs`. `MEGURI_BROWSER_CHANNEL=chromium` selects Playwright's bundled browser without a branded channel; unset selects locally installed Chrome. Individual suites must not hardcode launch channels. CI installs Chromium and sets this variable for the whole browser run. +Binding-preview regression uses the nested demo project objects, checking project names, ordered responsible people and their scoped identities. It also verifies that invalid field types retain a readable preview while server validation reports the error. + 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. See [JSM environment and acceptance preparation](jsm-acceptance.md) for current test-site options and the live verification sequence. diff --git a/static/generation.js b/static/generation.js index 20393ae..3975cc3 100644 --- a/static/generation.js +++ b/static/generation.js @@ -137,7 +137,7 @@ function generationUi(){return { const context={},names=[]; // Preview one row only; unrelated incomplete loops must not hide literal values. for(const iteration of this.form.iterations||[]){ - try{const source=this.sampleBinding(iteration.source,context);if(!source || typeof source!=='object')break;const entry=Object.entries(source)[0];if(!entry)break;const [key,value]=entry;context[iteration.id]={key:Array.isArray(source)?'':key,value};names.push(iteration.name+': '+(typeof value==='string'?value:key));}catch{break;} + try{const source=this.sampleBinding(iteration.source,context);if(!source || typeof source!=='object')break;const entry=Object.entries(source)[0];if(!entry)break;const [key,value]=entry;context[iteration.id]={key:Array.isArray(source)?'':key,value};names.push(iteration.name+': '+(typeof value==='string'?value:typeof value?.name==='string'?value.name:typeof value?.title==='string'?value.title:Array.isArray(source)?String(Number(key)+1):key));}catch{break;} } let value; if(binding.kind==='output'){ diff --git a/tests/browser-binding-preview.cjs b/tests/browser-binding-preview.cjs index 6247994..ef60033 100644 --- a/tests/browser-binding-preview.cjs +++ b/tests/browser-binding-preview.cjs @@ -14,7 +14,18 @@ const assert=require('node:assert/strict'); const projects=await page.evaluate(()=>Alpine.$data(document.body).generationDraft.variables.find(v=>v.name==='projects').id); await page.getByLabel('Binding variable',{exact:true}).selectOption(projects); await page.waitForFunction(()=>document.querySelector('.binding-value-preview textarea')?.value.includes('Payments')); - assert.deepEqual(JSON.parse(await preview.inputValue()),['Payments','Search','Billing','Identity','Data platform','Developer portal']); + const projectValues=JSON.parse(await preview.inputValue()); + assert.deepEqual(projectValues.map(project=>project.name),['Payments','Search','Billing','Identity','Data platform','Developer portal']); + assert.deepEqual(projectValues.map(project=>project.responsible.map(person=>person.username)),[ + ['alice','bob','dave'],['bob','erin'],['carol','alice','erin'], + ['dave','carol'],['erin','dave','bob'],['carol','erin'], + ]); + const directory=await page.evaluate(()=>Alpine.$data(document.body).people); + for(const project of projectValues)for(const person of project.responsible){ + const known=directory.find(candidate=>candidate.id===person.id); + assert(known,'Nested preview person must retain a real scoped identity'); + assert.equal(person.type,'person');assert.equal(person.name,known.display_name);assert.equal(person.username,known.username); + } await page.waitForFunction(()=>{const a=Alpine.$data(document.body);return !a.generationLoading && a.generationResult?.issues.some(i=>i.message.includes('repeat_count'));}); await page.screenshot({path:'test-results/binding-value-preview.png'}); await page.getByLabel('Binding variable',{exact:true}).selectOption('');await page.waitForFunction(()=>document.querySelector('.binding-value-preview textarea')?.value==='');