{"_id":"@ascendenceai/cortena-extensions-assure-reporters","_rev":"2-c095dde3edbac1d963d75aae1d94646d","name":"@ascendenceai/cortena-extensions-assure-reporters","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@ascendenceai/cortena-extensions-assure-reporters","version":"0.1.0","license":"SEE LICENSE IN LICENSE","_id":"@ascendenceai/cortena-extensions-assure-reporters@0.1.0","maintainers":[{"name":"amit_ascendence","email":"connect@mindmentors.net"}],"homepage":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions#readme","bugs":{"url":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions/issues"},"bin":{"assure-gate":"dist/gate-cli.js","assure-sync-cases":"dist/sync/cli.js"},"dist":{"shasum":"008e20fcefe4cd89ce3c1109ea7c761d674ee534","tarball":"https://registry.npmjs.org/@ascendenceai/cortena-extensions-assure-reporters/-/cortena-extensions-assure-reporters-0.1.0.tgz","fileCount":51,"integrity":"sha512-gi3mxFEFSMukgjxeg6AfAO7pST3/fZCpewTBUxujc7QCWarm+BSps6hcughyu0LUlTfEUwGTOPaQZpMte0FGJw==","signatures":[{"sig":"MEUCIQDZ18M9FtLYdrSeix9NIUpD2qvnGZDWu9q8Hs6u/vKOUAIgXM4lbaUN3tmL9o7ElORBkogzNQvV9EFLSMFzQ/YsXck=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":221566},"main":"dist/index.js","type":"module","_from":"file:ascendenceai-cortena-extensions-assure-reporters-0.1.0.tgz","types":"dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./gate":{"types":"./dist/gate.d.ts","default":"./dist/gate.js"},"./vitest":{"types":"./dist/vitest.d.ts","default":"./dist/vitest.js"},"./playwright":{"types":"./dist/playwright.d.ts","default":"./dist/playwright.js"},"./annotations":{"types":"./dist/annotations.d.ts","default":"./dist/annotations.js"},"./package.json":"./package.json"},"scripts":{"lint":"tsc --noEmit -p tsconfig.test.json","test":"vitest run","build":"tsc","typecheck":"tsc --noEmit -p tsconfig.test.json"},"_npmUser":{"name":"amit_ascendence","email":"connect@mindmentors.net"},"_resolved":"/private/var/folders/yz/wdclk8jx2l3c4bkzs3wg_0z80000gp/T/5ee6d25518cb0e663bf6c5a7e487d600/ascendenceai-cortena-extensions-assure-reporters-0.1.0.tgz","_integrity":"sha512-gi3mxFEFSMukgjxeg6AfAO7pST3/fZCpewTBUxujc7QCWarm+BSps6hcughyu0LUlTfEUwGTOPaQZpMte0FGJw==","repository":{"url":"git+https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions.git","type":"git","directory":"packages/assure-reporters"},"_npmVersion":"11.9.0","description":"Vitest and Playwright reporters that record an extension's test results in CortenaAssure, plus sync-cases and the release gate — §21.7, rule P-35.","directories":{},"_nodeVersion":"25.6.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/cortena-extensions-assure-reporters_0.1.0_1788785635976_0.8143652274709801","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"_id":"@ascendenceai/cortena-extensions-assure-reporters@0.4.0","bin":{"assure-gate":"dist/gate-cli.js","assure-sync-cases":"dist/sync/cli.js"},"bugs":{"url":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions/issues"},"dist":{"shasum":"04eabe2d49c44e7fc08ca49d7bc707ec02daf3b5","tarball":"https://registry.npmjs.org/@ascendenceai/cortena-extensions-assure-reporters/-/cortena-extensions-assure-reporters-0.4.0.tgz","fileCount":51,"integrity":"sha512-CXJa5+Xj5cf9mKNz0hJKVle2Km1noOblH+yLGgqk/n8ixfCyEZ7L7dS9DfeSZR5XK9sc13rMZXsoWmWoxEP4dg==","signatures":[{"sig":"MEUCIQCrlBR8jTRPQqzTAZ6h/dUgRlGDaLTyob2PcV3+zcNuBgIgFSAw3wDBSqxiJV6HTliIsZIaI6ixx2QUXjFqM/ArrVo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCHeNp/9RjXgpFKH9egk/ZqcOzcdlQqKazVzws5AiMDogIgIEzdwYOQre2oTiyXAnpj4OgRj1p3VJNkBiTjuCno9Oc="}],"unpackedSize":221566},"main":"dist/index.js","name":"@ascendenceai/cortena-extensions-assure-reporters","type":"module","_from":"file:ascendenceai-cortena-extensions-assure-reporters-0.4.0.tgz","types":"dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./gate":{"types":"./dist/gate.d.ts","default":"./dist/gate.js"},"./vitest":{"types":"./dist/vitest.d.ts","default":"./dist/vitest.js"},"./playwright":{"types":"./dist/playwright.d.ts","default":"./dist/playwright.js"},"./annotations":{"types":"./dist/annotations.d.ts","default":"./dist/annotations.js"},"./package.json":"./package.json"},"license":"SEE LICENSE IN LICENSE","scripts":{"lint":"tsc --noEmit -p tsconfig.test.json","test":"vitest run","build":"tsc","typecheck":"tsc --noEmit -p tsconfig.test.json"},"version":"0.4.0","_npmUser":{"name":"amit_ascendence","email":"connect@mindmentors.net"},"homepage":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions#readme","_resolved":"/private/var/folders/yz/wdclk8jx2l3c4bkzs3wg_0z80000gp/T/319a42ca5147b6ee82d4065516513a16/ascendenceai-cortena-extensions-assure-reporters-0.4.0.tgz","_integrity":"sha512-CXJa5+Xj5cf9mKNz0hJKVle2Km1noOblH+yLGgqk/n8ixfCyEZ7L7dS9DfeSZR5XK9sc13rMZXsoWmWoxEP4dg==","repository":{"url":"git+https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions.git","type":"git","directory":"packages/assure-reporters"},"_npmVersion":"11.9.0","description":"Vitest and Playwright reporters that record an extension's test results in CortenaAssure, plus sync-cases and the release gate — §21.7, rule P-35.","directories":{},"maintainers":[{"name":"amit_ascendence","email":"connect@mindmentors.net"}],"_nodeVersion":"25.6.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cortena-extensions-assure-reporters_0.4.0_1790491420748_0.5099502476937203"}}},"time":{"created":"2026-09-07T12:53:55.788Z","modified":"2026-09-27T06:43:41.003Z","0.1.0":"2026-09-07T12:53:56.110Z","0.4.0":"2026-09-27T06:43:40.852Z"},"bugs":{"url":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions/issues"},"license":"SEE LICENSE IN LICENSE","homepage":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions#readme","repository":{"url":"git+https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions.git","type":"git","directory":"packages/assure-reporters"},"description":"Vitest and Playwright reporters that record an extension's test results in CortenaAssure, plus sync-cases and the release gate — §21.7, rule P-35.","maintainers":[{"name":"amit_ascendence","email":"connect@mindmentors.net"}],"readme":"# @ascendenceai/cortena-extensions-assure-reporters\n\nVitest and Playwright reporters that record an extension's test results in\n[CortenaAssure](../../extensions/Assure), a `sync-cases` step that writes the\ncases, and a `gate` helper that reads the release verdict.\n\nProtocol reference: §21.7 of *How to create a Cortena extension*, audit rule\nP-35 (tests recorded in Assure) and P-36 (the end-to-end suite).\n\n## Why\n\nA green pipeline is a claim that evaporates. Nobody can ask it later which\nassertions covered a release, what evidence there was for a pass, or whether a\nfailure was ever explained. Assure can answer all three, and **Assure's release\ngate verdict — not a pipeline colour — is what says the extension may ship.**\n\nTwo rules follow from that, and this package exists to keep them:\n\n- **Assure executes nothing. CI executes.** The reporters record what already\n  happened. Nothing here runs a test, retries one, or decides a verdict.\n- **`errored` is not `failed`.** A suite that could not start is not a suite that\n  ran and disagreed. Collapsing the two gives a gate that cannot tell a broken\n  environment from a broken product.\n\nAnd one that follows from Assure: **a failure with no evidence is named when the\nrun closes.** Both reporters therefore attach at least a note to every failure,\nand Playwright attaches the screenshot, the trace and the console besides.\n\n## The environment contract\n\n| Variable | Meaning |\n| --- | --- |\n| `ASSURE_URL` | The API origin. **Must be `https:`** — the token is a bearer credential on every request — except a loopback host, which is exempt so the suites can use a real socket. `/v1` is appended if you leave it off. |\n| `ASSURE_TOKEN` | A cortena-auth bearer token carrying `run:execute` (and `case:write` for `sync-cases`). CI holds it as a secret. |\n| `ASSURE_APPLICATION_ID` | The extension's Assure application. |\n| `ASSURE_ENVIRONMENT_ID` | The environment under test — `cortena-test` for the post-deploy job. |\n| `ASSURE_ORG_ID` | Optional. Otherwise read from the token's `orgId` claim, since every route is under `/orgs/{orgId}`. |\n| `ASSURE_RELEASE_ID` | Optional, for the gate. Otherwise the newest release **on `ASSURE_ENVIRONMENT_ID`** — never one from another environment, so staging's verdict cannot answer production's question. No release for this environment is exit 2. |\n| `ASSURE_IDEMPOTENCY_KEY` | Optional. Assure requires one on run opening; a random one is used if you do not set it. |\n\n**Offline.** With `ASSURE_URL` or `ASSURE_TOKEN` unset the **reporters** print\none line and every call is a no-op, so `pnpm test` on a laptop needs no\ncredentials.\n\nThe two **commands** do not get that latitude. `assure-gate` and\n`assure-sync-cases` exit **2** offline, never 0: \"the gate is met\" and \"nobody\nread a gate\" must not be the same answer to CI, or a job that loses a renamed\nsecret goes green having checked nothing. A laptop that wants the tests simply\ndoes not run those commands.\n\n**Loud.** With those two set but an id missing, it throws before a single test is\nreported, naming both ids and which is missing. The application and environment\nrecords are created when the extension is onboarded into Assure; **the reporters\nnever create them.** An application invented by a CI job is an application nobody\nowns, and a second one appears the next time the variable is forgotten.\n\n## Opting a test in\n\nOnly annotated tests become cases. Everything else is counted into **one suite\ncase per package** as evidence that the suite ran — syncing every test produces\nthousands of rows nobody curated.\n\n```ts\nimport { assureCase } from '@ascendenceai/cortena-extensions-assure-reporters/annotations';\n\nassureCase({\n  assetKey: 'api:POST /v1/tasks',   // kind-prefixed: api|proc|screen|job|event|mod|schema\n  title:    'An unauthenticated POST is refused',\n  steps:    ['POST /v1/tasks with no Authorization header'],\n  expected: 'The response status is 401 and no task row is created',\n  segment:  'api',                  // screen | api | database | security | performance\n  checkKind: 'behavioural',         // optional: behavioural | measured | judged\n  // also optional: priority, securityClass, preconditions, notes, viewports, themes\n});\n```\n\n`expected` is the truth source and must be **decidable**: Assure refuses \"the\nresponse should be secure\" and refuses a clause that only restates the steps.\n\nWrite it as a literal call. `sync-cases` reads it out of the file without running\nthe suite, so the annotation and the case cannot drift — and a case for a broken\nroute is exactly the case you most want written down.\n\n### Layer 1 and 2 — Vitest\n\n```ts\nimport { test, expect } from 'vitest';\nimport { assureCase, assureAttach } from '@ascendenceai/cortena-extensions-assure-reporters/annotations';\n\ntest('an unauthenticated POST is refused', async (ctx) => {\n  assureAttach(ctx, assureCase({\n    assetKey: 'api:POST /v1/tasks',\n    title: 'An unauthenticated POST is refused',\n    steps: ['POST /v1/tasks with no Authorization header'],\n    expected: 'The response status is 401 and no task row is created',\n    segment: 'api',\n  }));\n\n  const res = await request(app).post('/v1/tasks').send({ title: 'x' });\n  expect(res.status).toBe(401);\n});\n```\n\n`assureAttach` writes to `ctx.task.meta`, which Vitest serialises across the\nworker boundary. It works with `test.extend` contexts too; `assureFixture` is\nexported for the fixture form.\n\n### Layer 3 — Playwright\n\n```ts\nimport { test, expect } from '@playwright/test';\nimport { assureCase, assureAnnotation } from '@ascendenceai/cortena-extensions-assure-reporters/annotations';\n\nconst board = assureCase({\n  assetKey: 'screen:/board',\n  title: 'The board renders in both themes',\n  steps: ['Sign in as the test user', 'Open /board'],\n  expected: 'The board heading is visible and no login screen is shown',\n  segment: 'screen',\n  checkKind: 'measured',\n});\n\ntest('the board renders', { annotation: [assureAnnotation(board)] }, async ({ page }) => {\n  await page.goto('/board');\n  await expect(page.getByRole('heading', { name: 'Board' })).toBeVisible();\n});\n```\n\nName your projects so they encode the variant — `chromium-dark-1280`,\n`web:light`, `e2e_light_mobile`. The reporters read `{ theme, viewport }` off the\nproject name, and a name encoding neither records no variant rather than a\nguessed one.\n\n### The suite case\n\n```ts\nimport { defineAssureSuite } from '@ascendenceai/cortena-extensions-assure-reporters/annotations';\ndefineAssureSuite('@cortena-extensions/tasks-functions');\n```\n\nThe reporters build this themselves from the nearest `package.json` name. Pass\n`suitePackage` to name it explicitly, or `suite: false` to switch it off.\n\n## Wiring the reporters\n\n```ts\n// vitest.config.ts\nexport default defineConfig({\n  test: {\n    reporters: [\n      'default',\n      ['@ascendenceai/cortena-extensions-assure-reporters/vitest', { suitePackage: '@your/package' }],\n    ],\n  },\n});\n```\n\n```ts\n// playwright.config.ts\nexport default defineConfig({\n  retries: 0,                      // a flaky test is a defect, not a retry\n  use: {\n    screenshot: 'only-on-failure',\n    trace: 'retain-on-failure',\n  },\n  projects: [\n    { name: 'chromium-light-1280', use: { ...devices['Desktop Chrome'], colorScheme: 'light' } },\n    { name: 'chromium-dark-1280',  use: { ...devices['Desktop Chrome'], colorScheme: 'dark' } },\n  ],\n  reporter: [\n    ['list'],\n    ['@ascendenceai/cortena-extensions-assure-reporters/playwright', { suitePackage: '@your/package-e2e' }],\n  ],\n});\n```\n\nReporter options are `RecorderOptions`: `suitePackage`, `suiteSegment`, `suite:\nfalse`, `scope`, `includeDrafts`, `fullEvidence`, `failOnUnmatchedCase`, `env`,\n`fetchImpl`, `log`.\n\n`failOnUnmatchedCase` **defaults to true**. An annotated test whose case is not\nin Assure records nothing at all, so the run says less than happened; a suite\nthat wants to tolerate the drift sets it to `false` on purpose.\n\n**Masking.** Every narrative, summary, failure detail and evidence caption is\nmasked on the way out: connection-string passwords, `Bearer …`, anything\nlabelled `token`/`secret`/`key`/`password`, and the `ASSURE_TOKEN` itself.\nWhat was removed is listed in the evidence's `redactions`. The record outlives\nthe credential in it, and everybody with access to the release can read it.\n\nIf Playwright is configured with retries anyway, only the first attempt is\nrecorded and the reporter says so: the record then states what happened rather\nthan laundering a real defect into a slower green.\n\n## `assure-sync-cases`\n\n```\nassure-sync-cases                 write and update, then submit the batch\nassure-sync-cases --check         report drift, write nothing\nassure-sync-cases --root ../..    scan from somewhere other than cwd\nassure-sync-cases --glob 'e2e/**/*.spec.ts'\nassure-sync-cases --suite @scope/pkg | --no-suite\n```\n\nIt scans the Vitest and Playwright globs, opens an Assure batch, writes each case\nwith `evidenceRefs` of path, line and the commit that last touched the file, and\nsubmits the batch. **Idempotent by asset key and title:** a case already saying\nthe same thing is left alone, which is what makes it safe on every pull request.\nA case whose annotation moved is patched — which returns it to review, because an\napproved case cannot change meaning without somebody reading it again.\n\nExit codes: `0` in sync, `1` drift or a refusal, `2` could not run.\n\nNew cases arrive as `waiting`. **A person still has to approve them**, and until\nthey do, results posted against them do not count toward a gate.\n\n## `assure-gate`\n\n```\nassure-gate [--release <id>]\n```\n\nExit codes, on the same discipline as the design gate (§22.1): `0` met, `1` not\nmet, `2` could not run. A gate that could not run must never look like a clean\none. The `readGate()` helper is exported for a job that wants the conditions.\n\n## CI wiring\n\nEverything except the end-to-end job runs on every pull request; the end-to-end\njob runs **after the test environment's deploy**, because there is nothing to\npoint a browser at until then (§21.4, §22).\n\n```yaml\nenv:\n  ASSURE_URL: ${{ vars.ASSURE_URL }}\n  ASSURE_TOKEN: ${{ secrets.ASSURE_TOKEN }}\n  ASSURE_APPLICATION_ID: ${{ vars.ASSURE_APPLICATION_ID }}\n  ASSURE_ENVIRONMENT_ID: ${{ vars.ASSURE_TEST_ENVIRONMENT_ID }}\n  ASSURE_IDEMPOTENCY_KEY: ${{ github.run_id }}-${{ github.job }}-${{ github.run_attempt }}\n\njobs:\n  functions:            # every pull request\n    steps:\n      - run: pnpm exec tsc --noEmit\n      - run: pnpm exec assure-sync-cases --check   # the catalogue matches the tests\n      - run: pnpm test --maxWorkers=2              # the Vitest reporter records the run\n\n  e2e:                  # after the test environment's deploy, never on a pull request\n    steps:\n      - run: pnpm exec playwright test             # both themes, artifacts kept on failure\n      - run: pnpm exec assure-gate                 # the shipping decision\n```\n\nRun `assure-sync-cases` (without `--check`) when annotations change — from a\nbranch, so the batch can be reviewed. Keep `--check` in the pull-request job so a\ntest whose annotation moved cannot merge with the catalogue silently behind it.\n\nKeep the artefacts. Playwright writes screenshots and traces under\n`test-results/`; the reporter records their **paths**, and if the job discards\nthem the reference outlives the file.\n\n## Known gaps in Assure's API\n\n- **No binary upload for evidence.** The OpenAPI document declares\n  `POST /results/{id}/evidence` as `multipart/form-data` with a `file`, but the\n  implemented route takes JSON `{ kind, uri, caption, sizeBytes, redactions }`\n  and stores a reference. Screenshots and traces are therefore attached **by\n  path or URL**, not uploaded. Point `uri` at a durable artefact store and the\n  evidence stays inspectable; leave it as a local path and it dies with the\n  runner. There is no `trace` kind either, so a Playwright trace is recorded as\n  `har`.\n- **The verdict belongs to a release, not a run.** There is no run-to-release\n  link in the API, so `assure-gate` takes `ASSURE_RELEASE_ID`, or the newest\n  release on the environment.\n- **A failure without evidence is reported at close, not refused at post.** The\n  OpenAPI says `POST /results` is 422 for a failure with no evidence; the\n  implementation checks at `POST /runs/{id}/close` and returns\n  `failuresWithoutEvidence`. Both reporters surface that list loudly.\n- **One open run per person per application.** Opening a second run on the same\n  application with the same token is a 409. Two jobs recording into Assure at\n  once need two tokens, or one run.\n\n## Exports\n\n| Entry point | What it is |\n| --- | --- |\n| `@ascendenceai/cortena-extensions-assure-reporters` | Everything below, plus `AssureClient`, `RunRecorder`, the types. |\n| `.../annotations` | `assureCase`, `assureAttach`, `assureFixture`, `assureAnnotation`, `defineAssureSuite`. |\n| `.../vitest` | The Vitest reporter (default export). |\n| `.../playwright` | The Playwright reporter (default export). |\n| `.../gate` | `readGate`, `formatGate`, `exitCodeFor`. |\n\nBinaries: `assure-sync-cases`, `assure-gate`.\n","readmeFilename":"README.md"}