Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,40 @@ This ensures:
- CI fails on new regressions (unexpected failures)
- CI fails when a fix lands but the baseline isn't updated (stale entries)

### Baselining a single check

A scenario is many checks — `server-stateless` alone is over twenty — so baselining the
whole scenario to excuse one of them stops enforcing the other nineteen. An entry can
instead name a single check, as `<scenario>:<check-id>`:

```yaml
server:
- tasks-lifecycle # whole scenario may fail
- server-stateless:sep-2575-server-implements-discover # only this check may fail
```

The check id is the left-hand column the runner already prints for each check, so it can
be copied straight out of a failing run.

With a per-check entry, every failing check in that scenario is judged on its own: the
named one is excused, and any other failure is still an unexpected regression. The four
exit-code rules above apply per check rather than per scenario, with one addition — a
baselined check that is absent or skipped is tolerated, because a scenario that bails on
a failed prerequisite legitimately never reaches its later checks, and the prerequisite
reports its own failure anyway.

Two things to know:

- **A check id addresses every occurrence of that id.** Ids repeat within a run (a loop,
a retried flow), and the occurrences collapse to one verdict, most-severe first. So
baselining a repeated id excuses all of its occurrences — coarser than ideal, still far
narrower than baselining the scenario.
- **Mind the space.** `- scenario:check-id` is a string; `- scenario: check-id` is YAML
for a mapping and is rejected with an error.

A scenario cannot be listed both wholesale and per-check — the wholesale entry already
excuses everything, so the pair is contradictory and is rejected.

## GitHub Action

This repo provides a composite GitHub Action so SDK repos don't need to write their own conformance scripts.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { collapseDuplicateChecks } from './dpop';
import type { ConformanceCheck, CheckStatus } from '../../../types';
import { collapseDuplicateChecks } from './collapse';
import type { ConformanceCheck, CheckStatus } from '../types';

/** Minimal check factory for the dedupe unit tests. */
function chk(id: string, status: CheckStatus, tag?: string): ConformanceCheck {
Expand All @@ -13,7 +13,7 @@ function chk(id: string, status: CheckStatus, tag?: string): ConformanceCheck {
};
}

describe('collapseDuplicateChecks (DPoP nonce-posture shared-check dedupe)', () => {
describe('collapseDuplicateChecks', () => {
it('collapses duplicate SUCCESS ids to a single entry', () => {
const out = collapseDuplicateChecks([
chk('token-request', 'SUCCESS'),
Expand Down
35 changes: 35 additions & 0 deletions src/checks/collapse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { CheckStatus, ConformanceCheck } from '../types';

/**
* Collapse duplicate non-INFO check IDs to a single entry, preferring the
* MOST-SEVERE occurrence (FAILURE > WARNING > SUCCESS > any other status, e.g.
* SKIPPED) so a real failure is never masked. Equal-severity ties keep the LAST
* occurrence. Per-request INFO log entries are always kept.
*
* A check ID is not unique within a run: scenarios re-emit a shared ID when a
* flow repeats. The RFC 9449 §8/§9 nonce round-trip re-POSTs /token (challenge
* → retry), so the shared token-flow checks (`token-request`, `pkce-*`) are
* appended twice; `sep-2575-http-server-meta-invalid-400` is emitted once per
* iteration of the `_meta` test-case loop. Collapsing reports each ID once
* without hiding a failure recorded on any occurrence, which is what lets an
* expected-failures baseline address a check by ID.
*/
export function collapseDuplicateChecks(
checks: ConformanceCheck[]
): ConformanceCheck[] {
const severity = (s: CheckStatus): number =>
s === 'FAILURE' ? 3 : s === 'WARNING' ? 2 : s === 'SUCCESS' ? 1 : 0;
// Winning index per non-INFO id: highest severity, ties → last occurrence.
const winner = new Map<string, number>();
checks.forEach((c, i) => {
if (c.status === 'INFO') return;
const cur = winner.get(c.id);
if (
cur === undefined ||
severity(c.status) >= severity(checks[cur].status)
) {
winner.set(c.id, i);
}
});
return checks.filter((c, i) => c.status === 'INFO' || winner.get(c.id) === i);
}
Loading
Loading