Artifacts published
38.2K12.0%
vs 34.1K last week
@@ -241,37 +79,49 @@Support backlog
270
Open tickets · unchanged week on week
What changed this week
+-
+
- Shared artifact links overtook partner integrations as the third-largest signup + channel, up 21.5% after link previews shipped on Monday. +
- Paid conversion slipped 0.3 points. The drop is concentrated in self-serve trials + that started during the 19 August ingest incident, not in the wider base. +
- The deploy freeze on ingest stays. Error budget is 81% consumed with 18 days to + run; the freeze lifts when it drops below 70%. +
Signups by channel
Week 34 · attributed on first touch
-| Channel | Sessions | -Signups | Conv. | -vs W33 |
|---|---|---|---|---|
| Docs & API reference | 48,210 | -1,904 | 3.9% | -+14.2% |
| Organic search | 39,884 | -1,201 | 3.0% | -+6.8% |
| Shared artifact links | 27,455 | -1,143 | 4.2% | -+21.5% |
| Partner integrations | 11,092 | -402 | 3.6% | -−2.1% |
| Community & social | 9,640 | -188 | 2.0% | -−8.4% |
| Channel | Sessions | +Signups | Conv. | +vs W33 |
| Docs & API reference | 48,210 | +1,904 | 3.9% | ++14.2% |
| Organic search | 39,884 | +1,201 | 3.0% | ++6.8% |
| Shared artifact links | 27,455 | +1,143 | 4.2% | ++21.5% |
| Partner integrations | 11,092 | +402 | 3.6% | +−2.1% |
| Community & social | 9,640 | +188 | 2.0% | +−8.4% |
| Total | 136,281 | -4,838 | 3.6% | -+9.7% |
| Total | 136,281 | +4,838 | 3.6% | ++9.7% |
Service levels
-What changed this week
--
-
- Shared artifact links overtook partner integrations as the third-largest signup - channel, up 21.5% after link previews shipped on Monday. -
- Paid conversion slipped 0.3 points. The drop is concentrated in self-serve trials - that started during the 19 August ingest incident, not in the wider base. -
- The deploy freeze on ingest stays. Error budget is 81% consumed with 18 days to - run; the freeze lifts when it drops below 70%. -
Checkout was unavailable for 3 hours 47 minutes on 12 September
- Incident
- INC-2026-041
- Status
- Resolved
- Authors
- D. Kaur, R. Idowu
- Reviewed
- 18 Sep 2026
A dependency bump raised the payment-provider timeout from two seconds to thirty. When the -provider slowed, every stuck request held its connection fifteen times longer and the pool -saturated — three lines inside a forty-one file pull request.
- +Checkout was unavailable for 3 hours 47 minutes on 12 September
+ -Impact
- Duration
- 3h 47m
09:14 – 13:01 UTC
Impact
A dependency bump raised the payment-provider timeout from two seconds to thirty. When the +provider slowed, every stuck request held its connection fifteen times longer and the pool +saturated — three lines inside a forty-one file pull request.
Timeline · 12 September, UTC
- 09:02@@ -303,29 +128,29 @@
What went well
- +
@@ -334,6 +159,6 @@Action items · 3 open, 1 complete - - -ID Action Owner -Due Status -AI-1 -Alert on p99 checkout latency and on success rate, not p50 alone. -M. Halvorsen 19 Sep -Done -AI-2 -Add a circuit breaker to the provider client, opening at 25% errors over 30 seconds. -R. Idowu 26 Sep -In progress +AI-3 -Treat timeout, retry and pool constants as behaviour changes in review — CODEOWNERS plus a + +ID Action Owner +Due Status +AI-1 +Alert on p99 checkout latency and on success rate, not p50 alone. +M. Halvorsen 19 Sep +Done +AI-2 +Add a circuit breaker to the provider client, opening at 25% errors over 30 seconds. +R. Idowu 26 Sep +In progress -AI-3 +Treat timeout, retry and pool constants as behaviour changes in review — CODEOWNERS plus a required note. -Platform 3 Oct -Not started +AI-4 -Rewrite the checkout runbook to diagnose saturation before scaling, + Platform 3 Oct +Not started +AI-4 +Rewrite the checkout runbook to diagnose saturation before scaling, pool metrics first. -D. Kaur 10 Oct -Not started D. Kaur 10 Oct +Not started What went well
Timestamps come from the incident channel and the deploy log; failure rates are the per-minute success-rate metric. - + diff --git a/templates/project-status.html b/templates/project-status.html index 0dacb7d..f3dab71 100644 --- a/templates/project-status.html +++ b/templates/project-status.html @@ -1,190 +1,25 @@ - - + +SOC 2 Type II readiness — status, week of 8 September - + -+- Project status · week 37 + -Project status · week 37SOC 2 Type II readiness
--
+
- Owner
- Priya Raman
- Reported
- 8 Sep 2026
- Next review
- 15 Sep
@@ -192,7 +27,7 @@SOC 2 Type II readiness
+ At risk
Changed from On track @@ -206,7 +41,35 @@At risk
vendor reassigned our team — that one change is why this is amber. --
+
-
+ 01
+ ++
Approve the second pen-test vendor, or accept the slip
+Decision by Priya Raman + Needed by 12 September
+The second vendor can start 22 September for £9,400 more. Doing nothing moves the + window to 15 November, pushing the report past the two enterprise renewals that asked + for it.
+
+ -
+ 02
+ ++
Sign off the standing-access model
+Decision by Marcus Lee & Security + Needed by 19 September
+Break-glass only, or named exceptions with a quarterly review — both are built. The + access review cannot close until one is chosen, and it is the last thing between us and + 104 of 104 controls.
+
+
+ + +Needs a human
+-
+
- Milestones
- 3 of 7
- Open blockers
- 3
- Days to window
- 54
-
-
Pen-test vendor cannot start before 6 October
+-Pen-test vendor cannot start before 6 October
BlockingNeeds: approval to run the test with our second vendor at +
Needs: approval to run the test with our second vendor at +£9,400, or a decision to move the observation window to 15 November.
- -
Nine engineers still hold standing production access
+-Nine engineers still hold standing production access
BlockingNeeds: the standing-access model signed off. Break-glass +
Needs: the standing-access model signed off. Break-glass tooling has been built and tested since 2 September — this is waiting on a decision, not on engineering.
- -
+ diff --git a/tests/e2e/template-table-semantics.spec.ts b/tests/e2e/template-table-semantics.spec.ts new file mode 100644 index 0000000..1acd302 --- /dev/null +++ b/tests/e2e/template-table-semantics.spec.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test'; + +const sandboxOrigin = process.env.E2E_CLOUD_SANDBOX_ORIGIN; + +for (const [slug, columns, expectedRows] of [ + ['metrics-dashboard', ['Channel', 'Sessions', 'Signups', 'Conv.', 'vs W33'], 6], + ['postmortem', ['ID', 'Action', 'Owner', 'Due', 'Status'], 4], +] as const) { + test(`${slug}: scoped native headers and accessible row/column relationships survive responsive display`, async ({ + page, + }) => { + await page.goto(`${sandboxOrigin}/templates/${slug}/frame`); + const table = page.getByRole('table'); + await expect(table).toHaveCount(1); + await expect(table.getByRole('columnheader')).toHaveText([...columns]); + const headers = table.locator('thead th'); + for (let index = 0; index < columns.length; index += 1) { + await expect(headers.nth(index)).toHaveAttribute('scope', 'col'); + } + const rows = table.getByRole('row'); + await expect(rows).toHaveCount(expectedRows + 1); + for (let index = 1; index <= expectedRows; index += 1) { + const row = rows.nth(index); + const rowHeader = row.getByRole('rowheader'); + await expect(rowHeader).toHaveCount(1); + expect(await rowHeader.evaluate((element) => element.tagName)).toBe('TH'); + await expect(rowHeader).toHaveAttribute('scope', 'row'); + const rowName = (await rowHeader.innerText()).trim(); + await expect(rowHeader).toHaveAccessibleName( + new RegExp(rowName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + ); + const cells = row.getByRole('cell'); + await expect(cells).toHaveCount(columns.length - 1); + // Native scope + table row membership + matching column order, not role presence alone. + // CSS-only mobile labels must agree with the retained (visually clipped) column headers. + for (let column = 1; column < columns.length; column += 1) { + const cell = cells.nth(column - 1); + const columnName = columns[column]; + if (!columnName) throw new Error('Missing column header'); + await expect(cell).toHaveAttribute('data-mobile-label', columnName); + const value = (await cell.textContent())?.trim().replace(/\s+/g, ' ') ?? ''; + await expect(cell).toHaveAccessibleName( + new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + ); + } + if (slug === 'postmortem') await expect(rowHeader).toHaveText(`AI-${index}`); + } + const tree = await table.ariaSnapshot(); + expect(tree).toContain('columnheader "'); + expect(tree).toContain('rowheader "'); + expect(tree).toContain('cell "'); + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe( + true + ); + }); +} diff --git a/tests/unit/template-table-semantics.test.ts b/tests/unit/template-table-semantics.test.ts new file mode 100644 index 0000000..bc4b0d6 --- /dev/null +++ b/tests/unit/template-table-semantics.test.ts @@ -0,0 +1,46 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +const source = (slug: string) => + readFileSync(new URL(`../../templates/${slug}.html`, import.meta.url), 'utf8'); + +// These two responsive tables intentionally retain explicit roles when CSS changes their display. +// Bound the exception to the known table inventory, rather than silencing future unrelated roles. +describe('responsive operational table semantics', () => { + it('uses four native scoped Postmortem row headers, not interactive roles on td', () => { + const html = source('postmortem'); + expect( + html.match( + /Two subprocessors have no countersigned DPA
+-Two subprocessors have no countersigned DPA
WatchingNeeds: countersignature from both vendors. Chased three +
Needs: countersignature from both vendors. Chased three times; escalate to their account teams if open on 22 September.
-- - -Needs a human
--
-
-
- 01
- --
Approve the second pen-test vendor, or accept the slip
-The second vendor can start 22 September for £9,400 more. Doing nothing moves the - window to 15 November, pushing the report past the two enterprise renewals that asked - for it.
-Decision by Priya Raman - Needed by 12 September
-
- -
- 02
- --
Sign off the standing-access model
-Break-glass only, or named exceptions with a quarterly review — both are built. The - access review cannot close until one is chosen, and it is the last thing between us and - 104 of 104 controls.
-Decision by Marcus Lee & Security - Needed by 19 September
-
-
AI-[1-4]<\/th>/g + ) + ).toHaveLength(4); + expect(html).not.toMatch(/ ]*role="rowheader"/); + }); + + for (const [slug, inventory] of [ + [ + 'metrics-dashboard', + { table: 1, rowgroup: 3, row: 7, columnheader: 5, rowheader: 6, cell: 24 }, + ], + ['postmortem', { table: 1, rowgroup: 2, row: 5, columnheader: 5, rowheader: 4, cell: 16 }], + ] as const) { + it(`${slug} limits its compatibility exception to the documented role inventory`, () => { + const html = source(slug); + expect(html.match(/biome-ignore[^\n]*/g)).toEqual([ + 'biome-ignore-all lint/a11y/noRedundantRoles: Explicit table roles retain semantics when mobile CSS uses block/grid display. -->', + ]); + const counts: Record = {}; + for (const match of html.matchAll( + /<(table|thead|tbody|tfoot|tr|th|td)\b[^>]*\brole="([^"]+)"/g + )) { + const role = match[2]; + if (!role) throw new Error('Missing explicit role capture'); + counts[role] = (counts[role] ?? 0) + 1; + } + expect(counts).toEqual(inventory); + expect(html.match(/role="[^"]+"/g)).toHaveLength( + Object.values(inventory).reduce((sum, count) => sum + count, 0) + 1 + ); // One unchanged named SVG image, outside the table. + }); + } +}); - -
complete · 1 in progress
1 needs a decision this week
@@ -258,65 +121,37 @@opens 1 November
Scope
Blockers