diff --git a/.gitignore b/.gitignore index 701ea31..5e6819f 100644 --- a/.gitignore +++ b/.gitignore @@ -112,9 +112,6 @@ jspm_packages/ /.vscode .cursor -# local development notes -docs/plans - # Output dist diff --git a/CHANGELOG.md b/CHANGELOG.md index 87bf234..576139d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,8 +32,7 @@ not run after a fatal failure. - Pure `foldCheckResults` helper and `computeId` namespace builder, both exported from the package barrel. - New consumer doc at `docs/api/verification-results.md` covering the result - shape, the `id` namespace, a UI rendering recipe, and a prompt-ready appendix - for downstream UIs. + shape, the `id` namespace, and a UI rendering recipe. - `timing?: boolean` flag on `VerifierConfig`, `VerifyCredentialCall`, and `VerifyPresentationCall`. When true, every `CheckResult`, every `SuiteSummary`, and every top-level diff --git a/README.md b/README.md index 4f66268..e5bd08c 100644 --- a/README.md +++ b/README.md @@ -849,8 +849,8 @@ embeds its suite's phase, so re-tagging a built-in suite would rename every check in it: changing either is a breaking change. See [`docs/api/verification-results.md`](./docs/api/verification-results.md) for -the full reference (phase model, `SuiteSummary` fields, `id` namespace, UI -rendering recipe, and a prompt-ready appendix for downstream UIs). +the full reference (phase model, `SuiteSummary` fields, `id` namespace, and a UI +rendering recipe). ## Capturing timing data @@ -871,7 +871,7 @@ console.log(result.summary[0].timing!.durationMs); Per-check timings ride on every row of `results[]`, and the per-suite rollup lives on `summary[i].timing`. See [`docs/api/timing.md`](./docs/api/timing.md) for the full reference, granularity -table, recipes, and a prompt-ready appendix. +table, and recipes. ## Pluggable clock (`TimeService`) @@ -1044,9 +1044,9 @@ for (const credResult of presResult.credentialResults) { } ``` -This is the recommended advanced integration shape for services like -`dcc-transaction-service` that verify a presentation up front and then inspect -each embedded credential's profile to drive downstream business logic. If +This is the recommended advanced integration shape for a service that verifies +a presentation up front and then inspects each embedded credential's profile to +drive downstream business logic. If performance matters, you're verifying high volumes of credentials, you want to show partial results to a user for verification in progress, or you have specific requirements for the verification process, this advanced integration diff --git a/docs/api/timing.md b/docs/api/timing.md index 19a97b6..7842905 100644 --- a/docs/api/timing.md +++ b/docs/api/timing.md @@ -3,11 +3,8 @@ > Consumer reference for the optional `timing` flag and the > `TaskTiming` data carried on every result when it is enabled. > -> **Audience.** Performance / observability work in -> `dcc-transaction-service`, `skills-verifier`, and any future -> client building on `verifier-core`. The §9 LLM-prompt -> appendix is written to be copy-pasted as the seed of an LLM -> prompt for client-side instrumentation code-gen. +> **Audience.** Performance and observability work in any client +> building on `verifier-core`. ## Table of Contents @@ -19,7 +16,6 @@ 6. [Parse-failure behavior](#parse-failure-behavior) 7. [Recipes](#recipes) 8. [`TimeService` reference](#timeservice-reference) -9. [Prompt-ready appendix](#prompt-ready-appendix) ## Overview @@ -245,75 +241,3 @@ verifier built on a `FakeTimeService` decides both at the pinned moment. Signature clock-skew windows and key rotation belong there too. Read it from `VerificationContext.timeService` inside `execute()`. - -## Prompt-ready appendix - -> Drop this section verbatim into an LLM prompt for client-side -> instrumentation code-gen. - -`@digitalcredentials/verifier-core` v2 emits optional -`TaskTiming` data on every result when constructed with -`timing: true`: - -```ts -import { createVerifier } from '@digitalcredentials/verifier-core'; -const verifier = createVerifier({ timing: true }); -const result = await verifier.verifyPresentation({ presentation }); -``` - -Every result then carries a `timing: TaskTiming` field at three -levels: - -```ts -interface TaskTiming { - startedAt: string; // ISO 8601 wall-clock - endedAt: string; // ISO 8601 wall-clock - durationMs: number; // monotonic, fractional ms - events?: ReadonlyArray; // reserved -} - -// Per-check (every row in results[] when timing: true): -result.results[i].timing -// Per-suite (always present in summary[] when timing: true): -result.summary[i].timing -// Top-level inclusive wrapper: -result.timing -// In verifyPresentation results, each embedded VC also has: -result.credentialResults[i].timing -``` - -Invariants (always hold when `timing: true`): - -1. `endedAt >= startedAt` for every `TaskTiming`. -2. `durationMs >= 0` for every `TaskTiming`. -3. Top-level `result.timing.durationMs >= max(s.timing.durationMs for s in result.summary)`. -4. `verifyPresentation` top-level `result.timing.durationMs >= cr.timing.durationMs` for every `cr in result.credentialResults`. -5. Per-suite `timing.durationMs == sum(child timing.durationMs)`. -6. Per-suite `timing.startedAt == min(child startedAt)`, - per-suite `timing.endedAt == max(child endedAt)`. - -Use `durationMs` for any sort / sum / comparison (it comes -from a monotonic clock); use `startedAt` / `endedAt` only for -log correlation and human-readable timelines. - -A "verification time breakdown" UI from a presentation result: - -```ts -function buildBreakdown(result: PresentationVerificationResult) { - const totalMs = result.timing!.durationMs; - return { - totalMs, - presentationOnlyMs: result.summary - .reduce((a, s) => a + (s.timing?.durationMs ?? 0), 0), - credentials: result.credentialResults.map(cr => ({ - id: cr.verifiableCredential.id, - totalMs: cr.timing!.durationMs, - bySuite: cr.summary.map(s => ({ - suite: s.id, - durationMs: s.timing!.durationMs, - fatalFailureAt: s.fatalFailureAt, - })), - })), - }; -} -``` diff --git a/docs/api/verification-results.md b/docs/api/verification-results.md index 019f444..369c204 100644 --- a/docs/api/verification-results.md +++ b/docs/api/verification-results.md @@ -5,9 +5,8 @@ > `id` namespace, the `SuiteSummary` rollup (new in v2.0.0), and a UI > rendering recipe. > -> **Audience.** UI/integration teams building on top of `verifier-core`, -> primarily `dcc-transaction-service`. The §8 prompt-ready appendix is -> written to be copy-pasted as the seed of an LLM prompt. +> **Audience.** UI and integration teams building on top of +> `verifier-core`. > > **See also.** For per-check / per-suite / per-call timing data > on every result, see [`timing.md`](./timing.md). @@ -23,7 +22,6 @@ 5. [`id` namespace reference](#id-namespace-reference) 6. [UI rendering recipe](#ui-rendering-recipe) 7. [Backwards compatibility](#backwards-compatibility) -8. [Prompt-ready appendix](#prompt-ready-appendix) ## Overview @@ -548,227 +546,3 @@ Lazy-expand them — most users only want the verdict + per-phase status. against the summary rollup, narrowing by `outcome.status`. See §6 step 3 for the canonical pattern. - -## Prompt-ready appendix - -Copy the fenced block below as the seed of an LLM prompt for the -dcc-transaction-service UI implementation. - -> Given the JSON shape and `SuiteSummary` type below, produce a React -> component that renders a presentation verification result. Group -> per-credential summaries by phase -> (`cryptographic` → `trust` → `recognition` → `semantic` → `unknown`). -> Show failure detail rows (from `results[]` filtered by `id` prefix -> **and** `outcome.status === 'failure'`) under the failing phase row, -> lazy-expanded. Colour a suite red only when it has `fatalFailureAt`; -> a suite with failures but no `fatalFailureAt` is a warning (amber), -> because non-fatal failures leave `verified` true. -> -> The result is the return value of -> `verifier.verifyPresentation({ presentation })` from -> `@digitalcredentials/verifier-core` (v2.0.0+). `results[]` carries -> every check — passes, skips with a reason, and rows not run after a -> fatal failure — so a row's `outcome.status` is what distinguishes -> them. -> -> `verified` means "no fatal check failed" (and, for a presentation, -> that every embedded credential is itself verified). The advisory -> (non-fatal) checks are `trust.registry.issuer`, -> `recognition.profile`, `cryptographic.status.list-issuer`, the Open -> Badges checks (the semantic `schema.obv3.*` checks and the Open -> Badges JSON-schema check), and any caller-supplied check without -> `fatal`. A credential whose only failing check is advisory is -> `verified: true` with `status: 'failure'` (or `'mixed'`) and no -> `fatalFailureAt` on that suite — render the failure rows as warnings, -> but do not render the verdict as failed. - ---- - -**JSON example.** - -```jsonc -{ - "verified": false, - "verifiablePresentation": { /* the VP you passed in, unchanged */ }, - "presentationResults": [ - { "id": "cryptographic.core.vp-structure", /* success */ }, - { "id": "cryptographic.proof.signature", /* success */ } - ], - "summary": [ - { "id": "cryptographic.proof", "phase": "cryptographic", - "suite": "proof", "status": "success", - "message": "1 of 1 check passed", - "counts": { "passed": 1, "failed": 0, "skipped": 0 } } - ], - "credentialResults": [ - { - // Only advisory checks failed, so the verdict stays true. - "verified": true, - "verifiableCredential": { /* VC with an unregistered issuer */ }, - "recognizedProfile": "obv3p0.openbadge", - "results": [ - { "id": "cryptographic.core.context-exists", /* success */ }, - { "id": "cryptographic.core.vc-context", /* success */ }, - { "id": "cryptographic.core.vc-structure", /* success */ }, - { "id": "cryptographic.core.credential-id", /* success */ }, - { "id": "cryptographic.core.proof-exists", /* success */ }, - { "id": "recognition.profile", /* success */ }, - { "id": "cryptographic.proof.signature", /* success */ }, - { "id": "trust.registry.issuer", - "suite": "registry", - "outcome": { "status": "failure", "problems": [ - { "type": "https://www.w3.org/TR/vc-data-model#ISSUER_NOT_REGISTERED", - "title": "Issuer Not Registered", - "detail": "Issuer DID not in any registry." } - ], - "payload": { "matches": [], "uncheckedRegistries": [] }}, - "fatal": false } - ], - "summary": [ - { "id": "cryptographic.core", "phase": "cryptographic", - "suite": "core", "status": "success", - "message": "5 of 5 checks passed", - "counts": { "passed": 5, "failed": 0, "skipped": 0 } }, - { "id": "recognition", "phase": "recognition", - "suite": "recognition", "status": "success", - "message": "1 of 1 check passed", - "counts": { "passed": 1, "failed": 0, "skipped": 0 } }, - { "id": "cryptographic.proof", "phase": "cryptographic", - "suite": "proof", "status": "success", - "message": "1 of 1 check passed", - "counts": { "passed": 1, "failed": 0, "skipped": 0 } }, - // Non-fatal failure: status "failure", but no fatalFailureAt - // and the credential is still verified. Render as a warning. - { "id": "trust.registry", "phase": "trust", - "suite": "registry", "status": "failure", - "message": "1 of 1 check failed", - "counts": { "passed": 0, "failed": 1, "skipped": 0 } } - ] - }, - { - // A fatal failure: this is what makes a verdict false. - "verified": false, - "verifiableCredential": { /* VC with a broken signature */ }, - "recognizedProfile": "obv3p0.openbadge", - "results": [ - { "id": "cryptographic.core.context-exists", /* success */ }, - { "id": "cryptographic.core.vc-context", /* success */ }, - { "id": "cryptographic.core.vc-structure", /* success */ }, - { "id": "cryptographic.core.credential-id", /* success */ }, - { "id": "cryptographic.core.proof-exists", /* success */ }, - { "id": "recognition.profile", /* success */ }, - { "id": "cryptographic.proof.signature", - "suite": "proof", - "outcome": { "status": "failure", "problems": [ - { "type": "https://www.w3.org/TR/vc-data-model#INVALID_SIGNATURE", - "title": "Invalid Signature", - "detail": "The credential's signature did not verify." } - ]}, - "fatal": true }, - { "id": "cryptographic.status.bitstring", - "suite": "status", - "outcome": { "status": "skipped", - "reason": "Not run: proof.signature failed" } }, - { "id": "cryptographic.status.list-issuer", - "suite": "status", - "outcome": { "status": "skipped", - "reason": "Not run: proof.signature failed" } }, - { "id": "trust.registry.issuer", - "suite": "registry", - "outcome": { "status": "skipped", - "reason": "Not run: proof.signature failed" } } - ], - "summary": [ - { "id": "cryptographic.core", "phase": "cryptographic", - "suite": "core", "status": "success", - "message": "5 of 5 checks passed", - "counts": { "passed": 5, "failed": 0, "skipped": 0 } }, - { "id": "recognition", "phase": "recognition", - "suite": "recognition", "status": "success", - "message": "1 of 1 check passed", - "counts": { "passed": 1, "failed": 0, "skipped": 0 } }, - // The fatal failure: the one summary with fatalFailureAt. - { "id": "cryptographic.proof", "phase": "cryptographic", - "suite": "proof", "status": "failure", - "message": "1 of 1 check failed", - "counts": { "passed": 0, "failed": 1, "skipped": 0 }, - "fatalFailureAt": "cryptographic.proof.signature" }, - { "id": "cryptographic.status", "phase": "cryptographic", - "suite": "status", "status": "skipped", - "message": "status not run: proof.signature failed", - "counts": { "passed": 0, "failed": 0, "skipped": 2 } }, - { "id": "trust.registry", "phase": "trust", - "suite": "registry", "status": "skipped", - "message": "registry not run: proof.signature failed", - "counts": { "passed": 0, "failed": 0, "skipped": 1 } } - ] - } - ] -} -``` - -**`SuiteSummary` type.** - -```ts -type SuiteSummaryPhase = - | 'cryptographic' - | 'trust' - | 'recognition' - | 'semantic' - | 'unknown'; - -interface SuiteSummary { - id: string; - phase: SuiteSummaryPhase; - suite: string; - status: 'success' | 'failure' | 'skipped' | 'mixed'; - message: string; - counts: { passed: number; failed: number; skipped: number }; - fatalFailureAt?: string; -} -``` - -**Grouping snippet.** - -```ts -const PHASE_ORDER: SuiteSummary['phase'][] = [ - 'cryptographic', - 'trust', - 'recognition', - 'semantic', - 'unknown', -]; - -function groupByPhase(cr: CredentialVerificationResult) { - const groups = new Map(); - for (const phase of PHASE_ORDER) groups.set(phase, []); - for (const s of cr.summary) groups.get(s.phase)!.push(s); - return groups; -} - -function failureDetail( - cr: CredentialVerificationResult, - failing: SuiteSummary, -) { - return cr.results.filter( - r => r.id.startsWith(failing.id + '.') && r.outcome.status === 'failure', - ); -} -``` - -**Layout reference.** - -``` -Presentation: ✗ (1 of 2 credentials verified) -├─ VP signature: ✓ -├─ Credential 1 (obv3p0.openbadge): ✓ — verified, with advisory failures -│ ├─ cryptographic ✓ -│ ├─ trust ✗ (1 of 1 check failed — advisory) -│ │ └─ trust.registry.issuer — Issuer Not Registered -│ └─ recognition ✓ -└─ Credential 2 (obv3p0.openbadge): ✗ - ├─ cryptographic ✗ (1 of 1 check failed) - │ └─ cryptographic.proof.signature — Invalid Signature - ├─ trust — (not run after fatal) - └─ recognition ✓ -``` diff --git a/docs/architecture.md b/docs/architecture.md index f5e9afb..cb6ceca 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -628,7 +628,7 @@ by `summary[]`, a per-suite rollup. The rollup is computed by a pure helper — that append late results and want to recompute their summaries. See [`docs/api/verification-results.md`](api/verification-results.md) for the full reference, -including the `id` namespace, rendering recipes, and the LLM prompt appendix for downstream UIs. +including the `id` namespace and rendering recipes. Both shapes use the suite-based `CheckResult[]` model. Each `CheckResult` carries a discriminated `CheckOutcome` (`success | failure | skipped`) plus provenance (suite, check id, fatal flag) and, @@ -743,8 +743,8 @@ without network dependencies, and composable — consumers wire in exactly the b recursion guard. - **`RegistryHandler` is a port.** Callers can override or extend the per-type handler map by passing `registryHandlers` on `VerifierConfig`. -- **No dynamic imports in `src/`.** The proof suite is statically wired; recursion through the - verifier replaced the old dynamic-import seam from `vc-recognition-handler` into `verify-suite`. +- **No dynamic imports in `src/`.** The proof suite is statically wired, and `vc-recognition-handler` + reaches the verifier through an injected thunk rather than by importing `verify-suite` at runtime. ### What's not yet hexagonal @@ -752,7 +752,8 @@ without network dependencies, and composable — consumers wire in exactly the b implementation embeds `@digitalcredentials/vc` directly inside `DataIntegrityCryptoService`. Replacing the default crypto service requires understanding the LD-Proofs / Data Integrity internals. The default crypto service verifies signatures only — credential status checking - is the sole responsibility of the status suite (P-E, 2026-04-19). + is the sole responsibility of the status suite, so the status list is fetched once per + verification and one answer, not two, is reported for a credential's status. - **Status suite** still consumes `@digitalcredentials/vc-bitstring-status-list` for purpose matching, list type, bitstring decoding, and index reading. The list's validity dates are checked by `@digitalcredentials/vc` inside the crypto service, with its proof, and issuer diff --git a/src/crypto-dispatch.ts b/src/crypto-dispatch.ts index 0943783..361fc53 100644 --- a/src/crypto-dispatch.ts +++ b/src/crypto-dispatch.ts @@ -1,9 +1,9 @@ /** * Shared crypto-service dispatch. * - * Presentation proofs, credential proofs, and (after the status-check - * rewrite) status-list-credential proofs all resolve through this helper - * against the same `CryptoService[]`. Callers own their problem types: + * Presentation proofs, credential proofs, and status-list-credential + * proofs all resolve through this helper against the same + * `CryptoService[]`. Callers own their problem types: * the helper reports a neutral outcome so a bad signature on a credential * and a bad signature on its status list credential stay distinct * diagnostics. diff --git a/src/fold-results.ts b/src/fold-results.ts index 6e52a96..b6881e4 100644 --- a/src/fold-results.ts +++ b/src/fold-results.ts @@ -5,9 +5,9 @@ * Called from `verifier.ts` after `runSuites` returns and after * the recognition payload has been lifted onto the result. The * helper has no I/O, no async, and no service dependencies — it - * is safe to call from any context (incl. consumers recomputing - * summaries after appending late results, e.g. - * dcc-transaction-service's async OpenBadges merge). + * is safe to call from any context, including consumers + * recomputing summaries after appending results that arrived + * later (an asynchronous OpenBadges merge, say). * * `results[]` is the raw input, unchanged: every check that ran, * was skipped, or was not run after a fatal failure. diff --git a/src/openbadges/achieved-level-check.ts b/src/openbadges/achieved-level-check.ts index 46f4050..a731a2a 100644 --- a/src/openbadges/achieved-level-check.ts +++ b/src/openbadges/achieved-level-check.ts @@ -27,7 +27,7 @@ import { formatJsonPointer } from '../util/json-pointer.js'; * - The credential has no `achievement.resultDescription[]` (nothing * to validate against). * - * Out of scope (intentional non-flagging — see plan Phase 3): + * Out of scope (intentional non-flagging): * - A `Result` that carries `achievedLevel` but is missing * `resultDescription` entirely. This is a different rule * ("missing reference"); the `result-ref` check only flags diff --git a/src/openbadges/index.ts b/src/openbadges/index.ts index 1f9929b..28df291 100644 --- a/src/openbadges/index.ts +++ b/src/openbadges/index.ts @@ -7,8 +7,7 @@ * passed explicitly via a verify call's `additionalSuites` (or * composed into a custom suite). * - * Surface follows the design in - * `docs/plans/2026-04-18-openbadges-module/00-design.md`: + * The surface is: * * - **Suites** (most common entry point): * {@link openBadgesSuite}, {@link openBadgesSemanticSuite}, diff --git a/src/openbadges/openbadges-suite.ts b/src/openbadges/openbadges-suite.ts index 281ac2f..2428a56 100644 --- a/src/openbadges/openbadges-suite.ts +++ b/src/openbadges/openbadges-suite.ts @@ -16,9 +16,9 @@ * for consumers who want full OB coverage and accept the schema * suite's network cost. * - * None of these are part of the verifier's default suite set; the - * 2026-04-18 plan ([Q2](../../docs/plans/2026-04-18-openbadges-module/00-questions.md#q2-default-path-behavior)) - * makes OpenBadges verification fully opt-in. To enable: + * None of these are part of the verifier's default suite set: + * OpenBadges verification is fully opt-in, so a caller who does not + * ask for it pays none of its cost. To enable: * * @example * ```ts diff --git a/src/openbadges/openbadges-zod.ts b/src/openbadges/openbadges-zod.ts index fa0c9ff..c517034 100644 --- a/src/openbadges/openbadges-zod.ts +++ b/src/openbadges/openbadges-zod.ts @@ -14,13 +14,12 @@ * fail the whole credential and silently skip the check. Callers filter at * the point of use. * - * These shapes are **not** exported from the submodule barrel in this plan — - * they are an internal implementation detail of the checks. A future effort can - * promote them (or a curated subset) to the public surface if it would be - * useful for consumer business logic. + * These shapes are **not** exported from the submodule barrel — they are an + * internal implementation detail of the checks. A future effort can promote + * them (or a curated subset) to the public surface if it would be useful for + * consumer business logic. * - * Shape coverage tracks what every check planned in the - * 2026-04-18-openbadges-module plan needs to read: + * Shape coverage tracks what each check needs to read: * * - `Obv3ResultShape` — `result-ref`, `achieved-level`, * `missing-result-status` @@ -33,9 +32,9 @@ * * ## Why these are kept alongside the strict `Obv3p0OpenBadgeCredentialSchema` * - * The 2026-04-18 P-C plan introduced a strict envelope schema in - * `schemas/classes-v3p0.ts` and `schemas/openbadge-credential-v3p0.ts` that - * validates an OB 3.0 credential top-to-bottom. That schema powers the + * `schemas/classes-v3p0.ts` and `schemas/openbadge-credential-v3p0.ts` hold a + * strict envelope schema that validates an OB 3.0 credential + * top-to-bottom. That schema powers the * recognition pipeline (`obv3p0Recognizer`) and the public * `recognition.profile` check. * diff --git a/src/openbadges/problem-types.ts b/src/openbadges/problem-types.ts index 5119f49..3718ffc 100644 --- a/src/openbadges/problem-types.ts +++ b/src/openbadges/problem-types.ts @@ -10,9 +10,9 @@ * `OpenBadgesProblemTypes`. * * Wire URIs use the same `https://www.w3.org/TR/vc-data-model#…` - * placeholder host as the rest of the catalog. See - * `docs/plans/2026-04-17-architecture-readiness-review/draft-ob-spec-issue-problem-details-registry.md` - * for why we treat these as opaque keys until a real registry exists. + * placeholder host as the rest of the catalog. No registry of Open + * Badges problem types exists yet, so consumers should treat these + * as opaque keys and match on them exactly. */ export const Obv3ProblemTypes = { diff --git a/src/openbadges/recognizers.ts b/src/openbadges/recognizers.ts index 6602e02..ccfba70 100644 --- a/src/openbadges/recognizers.ts +++ b/src/openbadges/recognizers.ts @@ -17,8 +17,6 @@ * exclusive (an EndorsementCredential isn't an OpenBadgeCredential * and vice versa), so configuration order doesn't affect which one * fires for a given credential. - * - * @see `docs/plans/2026-04-18-openbadges-recognizer-and-subchecks/00-design.md` */ import type { RecognizerSpec } from '../types/recognition.js'; diff --git a/src/openbadges/schemas/classes-v3p0.ts b/src/openbadges/schemas/classes-v3p0.ts index 5ae72ee..4e92a00 100644 --- a/src/openbadges/schemas/classes-v3p0.ts +++ b/src/openbadges/schemas/classes-v3p0.ts @@ -79,13 +79,13 @@ export function ImageField() { * Required: `id` (IRI; DIDs accepted), `type` (must include * `'Profile'`). * - * In-scope optional fields per the design (Q4.c): `name`, `url`, - * `description`, `image`, `email`. Email validation is light - * (`@`-presence) — full RFC-5321 conformance is out of scope. + * Optional fields in scope: `name`, `url`, `description`, + * `image`, `email`. Email validation is light (`@`-presence) — + * full RFC-5321 conformance is out of scope. * * Recursive Profile-typed fields (e.g. `parentOrg`) stay * passthrough; supporting them needs `z.lazy()` and is out of - * scope per the parent plan's Q4.c. + * scope. * * `id` uses {@link IriString} rather than `z.string().url()` so * `did:`-prefixed identifiers (the most common issuer form) are @@ -219,10 +219,10 @@ export type Obv3p0ResultDescription = z.infer< * * **Note**: `Result.resultDescription` is a string IRI cross- * referencing an entry in `Achievement.resultDescription[]`. The - * existing `obv3-result-ref-check` (refactored in Phase 8 to use - * the normalized form) verifies that the reference resolves; - * this schema only validates that the field is a syntactically - * valid URL string. + * `schema.obv3.result-ref` check verifies that the reference + * resolves, parsing with the tolerant shapes in + * `openbadges-zod.ts` rather than this schema; this schema only + * validates that the field is a syntactically valid URL string. * * Backfilled into {@link Obv3p0AchievementSubjectSchema}.`result[]`. */ @@ -265,9 +265,9 @@ const Obv3p0CriteriaSchema = z * - `creator` — Profile (normalized via {@link ProfileRefField}). * - `image` — Image (normalized via {@link ImageField}). * - `resultDescription[]` — array of - * {@link Obv3p0ResultDescriptionSchema} (Phase 7). + * {@link Obv3p0ResultDescriptionSchema}. * - * Wired into `AchievementSubject.achievement` in Phase 6. + * Reached from `AchievementSubject.achievement`. */ export const Obv3p0AchievementSchema = z .object({ @@ -289,9 +289,8 @@ export type Obv3p0Achievement = z.infer; * Open Badges 3.0 §B.1.12 — IdentityObject (file-local). * * Used inside `AchievementSubject.identifier[]` entries. Kept - * light per Q4.c — passthrough with a required `type` - * discriminator. Full hashed-identity validation (§9.3) is out of - * scope. + * light — passthrough with a required `type` discriminator. Full + * hashed-identity validation (§9.3) is out of scope. */ const Obv3p0IdentityObjectSchema = z .object({ @@ -313,7 +312,7 @@ const Obv3p0IdentityObjectSchema = z * `/credentialSubject` once backfilled into the envelope. * * Optional in scope: `result[]` (array of - * {@link Obv3p0ResultSchema}, Phase 7), `image` (normalized via + * {@link Obv3p0ResultSchema}), `image` (normalized via * {@link ImageField}), `identifier[]` (entries kept light per the * IdentityObject note above). */ diff --git a/src/openbadges/schemas/openbadge-credential-v3p0.ts b/src/openbadges/schemas/openbadge-credential-v3p0.ts index f289fc4..f3aa35a 100644 --- a/src/openbadges/schemas/openbadge-credential-v3p0.ts +++ b/src/openbadges/schemas/openbadge-credential-v3p0.ts @@ -1,13 +1,13 @@ /** * Strict Zod schema for the OB 3.0 OpenBadgeCredential envelope. * - * Phase-2 scope: top-level envelope only. Inner classes - * (`AchievementSubject`, `Achievement`, `Profile`, `Image`, - * `Result`, etc.) stay `passthrough` and are filled in by Phases - * 3–7. The cross-field VCDM v1/v2 date discriminator - * (`issuanceDate` vs `validFrom`) lives here. - * - * @see `docs/plans/2026-04-18-openbadges-recognizer-and-subchecks/02-envelope-and-date-discriminator.md` + * This module owns the top-level envelope: the `@context` array, + * `id`, `type`, `issuer`, `image`, the date fields, and the + * cross-field VCDM v1/v2 date discriminator (`issuanceDate` vs + * `validFrom`). Inner classes (`AchievementSubject`, + * `Achievement`, `Profile`, `Image`, `Result`, etc.) are defined + * in `classes-v3p0.ts` and composed in here, so a credential + * validates top-to-bottom. */ import { z } from 'zod'; @@ -50,8 +50,7 @@ const TypeField = JsonLdTypeField(['VerifiableCredential']).superRefine( * this, `tsc --declaration` runs into TS7056 ("inferred type * exceeds the maximum length the compiler will serialize") * because the union/refine layers compound across every nested - * class schema (Phase 7 was the straw that broke the camel's - * back). Runtime behavior is unaffected; consumers narrow + * class schema. Runtime behavior is unaffected; consumers narrow * `Obv3p0OpenBadgeCredential` (typed as a permissive record * below) at use, typically via `recognizedProfile`. */ diff --git a/src/problem-types.ts b/src/problem-types.ts index aa92564..37fd398 100644 --- a/src/problem-types.ts +++ b/src/problem-types.ts @@ -14,13 +14,13 @@ * Of those four, this catalog currently emits only PARSING_ERROR. * The other three are listed in the JSDoc above for awareness; we * may align synthesized tokens like INVALID_SIGNATURE onto - * CRYPTOGRAPHIC_SECURITY_ERROR in a future deliberate refactor (out - * of scope for P-D — see the design doc's non-goals). + * CRYPTOGRAPHIC_SECURITY_ERROR in a future deliberate refactor; + * doing so would change values consumers match on, so it is not + * done casually. * * **Synthesized placeholders** — temporary URIs we use until the * W3C VC working group publishes a wider error registry or we adopt - * our own URN namespace (see the readiness review's Q9 for the - * decision history). They reuse the same + * our own URN namespace. They reuse the same * `https://www.w3.org/TR/vc-data-model#…` prefix as a stable string * but are NOT defined by the spec; treat them as opaque keys. * diff --git a/src/services/data-integrity-crypto.ts b/src/services/data-integrity-crypto.ts index ecac6a6..8329e63 100644 --- a/src/services/data-integrity-crypto.ts +++ b/src/services/data-integrity-crypto.ts @@ -25,11 +25,11 @@ import { isVerificationMethodMessage } from '../util/verification-method-message * for any VC with `credentialStatus`; this no-op satisfies that * required signature without performing any real status work. * - * Real status verification is owned by `statusSuite` - * (`src/suites/status/bitstring-status-check.ts`). See plan - * `docs/plans/2026-04-19-route-via-http-and-dedupe-status-check/` - * for the rationale (P-E: status is owned solely by the status - * suite, no double-check from the proof path). + * Real status verification is owned solely by `statusSuite` + * (`src/suites/status/bitstring-status-check.ts`), so the proof + * path must not check status a second time: a double check would + * fetch the status list twice and could report two different + * answers for one credential. */ const noopCheckStatus = async (): Promise<{ verified: true; results: [] }> => ({ verified: true, diff --git a/src/services/time-service/fake-time-service.ts b/src/services/time-service/fake-time-service.ts index 134d369..20192af 100644 --- a/src/services/time-service/fake-time-service.ts +++ b/src/services/time-service/fake-time-service.ts @@ -20,13 +20,11 @@ import type { TimeService } from './time-service.js'; * wall-clock and performance reads can interleave without * skewing each other's expected values in tests. * - * Diverges from the skills-verifier `FakeTimeService` (which - * passes `performanceNowMs()` through to real - * `performance.now()`) on purpose: verifier-core uses - * `performanceNowMs()` to populate `TaskTiming.durationMs`, - * and shape-only duration assertions are a poor substitute - * for the deterministic exact-value assertions consumers - * actually want when verifying timing rollup math. + * `performanceNowMs()` is counted rather than passed through + * to the real `performance.now()` on purpose: it populates + * `TaskTiming.durationMs`, and shape-only duration assertions + * are a poor substitute for the deterministic exact-value + * assertions that timing rollup math needs. */ export function FakeTimeService( opts: { diff --git a/src/suites/status/bitstring-status-check.ts b/src/suites/status/bitstring-status-check.ts index 2edeb7c..1d70efa 100644 --- a/src/suites/status/bitstring-status-check.ts +++ b/src/suites/status/bitstring-status-check.ts @@ -22,7 +22,9 @@ import { loadStatusList } from './status-lists.js'; -// Error patterns from constants/external.ts +// Error strings raised by `@digitalcredentials/vc-bitstring-status-list`, +// matched to classify its rejections. They are not part of that package's +// typed surface, so they are pinned here as literals. const NOT_FOUND_ERROR = 'NotFoundError'; const STATUS_TYPE_ERROR = 'Status list credential type must include "BitstringStatusListCredential".'; diff --git a/src/types/check.ts b/src/types/check.ts index 45995c7..f4b933c 100644 --- a/src/types/check.ts +++ b/src/types/check.ts @@ -7,7 +7,7 @@ * iterates suites and assembles a flat `CheckResult[]` report. * * @see {@link ../run-suites.ts} for orchestration - * @see {@link ../defaults.ts} for the default suite ordering + * @see {@link ../default-suites.ts} for the default suite ordering */ import type { ProblemDetail } from './problem-detail.js'; diff --git a/src/types/crypto-service.ts b/src/types/crypto-service.ts index 86c91c8..dae0feb 100644 --- a/src/types/crypto-service.ts +++ b/src/types/crypto-service.ts @@ -3,7 +3,7 @@ * * Implementations wrap concrete libraries (e.g. `@digitalcredentials/vc` for Data * Integrity). The proof suite dispatches to `CryptoService` instances from - * `VerificationContext` (phase 2). + * `VerificationContext`. */ import type { DocumentLoader } from './context.js'; diff --git a/src/types/recognition.ts b/src/types/recognition.ts index ab2f6d0..0492b78 100644 --- a/src/types/recognition.ts +++ b/src/types/recognition.ts @@ -8,8 +8,6 @@ * applies-true match's normalized form on * {@link CredentialVerificationResult.normalizedVerifiableCredential} * + {@link CredentialVerificationResult.recognizedProfile}. - * - * @see `docs/plans/2026-04-18-openbadges-recognizer-and-subchecks/00-design.md` */ import type { ProblemDetail } from './problem-detail.js'; diff --git a/src/types/timing.ts b/src/types/timing.ts index 02b5dd5..c227794 100644 --- a/src/types/timing.ts +++ b/src/types/timing.ts @@ -15,10 +15,9 @@ * - `events` — reserved for an ordered series of sub-events * captured from inside one task (e.g. `signature.verify`, * `documentLoader.load`, `statusList.fetch`). No built-in - * capture writes to this field today; a future plan may - * add a hook on `VerificationContext` for checks to push - * sub-events. Recursive so a sub-event can have its own - * sub-events. + * capture writes to this field today; a future hook on + * `VerificationContext` could let checks push sub-events. + * Recursive so a sub-event can have its own sub-events. * * @see ../../docs/api/timing.md for usage patterns. */ diff --git a/src/util/did-web-driver-with-http-get.ts b/src/util/did-web-driver-with-http-get.ts index 93efcc7..f0a16c7 100644 --- a/src/util/did-web-driver-with-http-get.ts +++ b/src/util/did-web-driver-with-http-get.ts @@ -1,8 +1,8 @@ /** * did:web resolution using caller-provided {@link HttpGetService} so DID document - * fetches share the same cache as JSON-LD context loads (e.g. Keyv in - * transaction-service). The stock {@link DidWebDriver} uses - * `@digitalcredentials/http-client` and bypasses that cache. + * fetches share the same cache as JSON-LD context loads (a Keyv-backed + * cache in a long-running service, say). The stock {@link DidWebDriver} + * uses `@digitalcredentials/http-client` and bypasses that cache. * * Fragment dereference logic matches `@digitalcredentials/did-method-web` * `getNode` (suite context map aligned with that package). diff --git a/src/verifier.ts b/src/verifier.ts index 8c00df9..a31fa6e 100644 --- a/src/verifier.ts +++ b/src/verifier.ts @@ -33,14 +33,14 @@ * not name, and a `.transform` can rewrite scalars into arrays. Either change * alters the canonicalized N-Quads, which makes a valid proof fail as * `INVALID_SIGNATURE` — a failure that looks like a bad credential and is - * actually a bug here. That is not hypothetical: a spec-legal - * `issuer.image.caption` was once dropped by a nested issuer image schema - * and rejected production Open Badges credentials. + * actually a bug here. A nested issuer image schema that drops a spec-legal + * `issuer.image.caption`, for instance, rejects Open Badges credentials that + * are perfectly valid. * * Returning the original object also matters downstream, because consumers - * re-verify `result.verifiableCredential` (the async Open Badges pass in - * dcc-transaction-service does exactly this). Handing them a rewritten copy - * makes the second pass fail where the first passed. + * re-verify `result.verifiableCredential` — an asynchronous second Open + * Badges pass, say. Handing them a rewritten copy makes that pass fail where + * the first passed. * * Suites must therefore tolerate raw JSON-LD shapes rather than assume * normalization: `@context` and `type` may each be a string or an array. diff --git a/test/factories/services/build-test-context.ts b/test/factories/services/build-test-context.ts index fe70c0c..ee78bc2 100644 --- a/test/factories/services/build-test-context.ts +++ b/test/factories/services/build-test-context.ts @@ -1,16 +1,15 @@ /** * Test-only helper that assembles a {@link VerificationContext} with - * sensible defaults — equivalent to the old `buildContext()` from - * `src/defaults.ts` (deleted in P-A phase 4). + * sensible defaults. * * Production code does not use this; the public path is * `createVerifier(...)`. Suite unit tests use it because they exercise * a single suite against a hand-built context, not the full pipeline. * - * Precedence matches the post-P-E production factory: explicit - * overrides win, otherwise the document loader and JSON fetcher are - * always derived from the effective `httpGetService` (default or - * override). No "first-time caller bypass" branch. + * Precedence matches `createVerifier`: explicit overrides win, + * otherwise the document loader and JSON fetcher are always derived + * from the effective `httpGetService` (default or override), so a + * test's injected HTTP service sees every fetch. */ import { Ed25519Signature2020 } from '@digitalcredentials/ed25519-signature-2020'; diff --git a/test/openbadges/fixtures/obv3p0-openbadge-spec-conforming.ts b/test/openbadges/fixtures/obv3p0-openbadge-spec-conforming.ts index 2ac0592..dd12d0d 100644 --- a/test/openbadges/fixtures/obv3p0-openbadge-spec-conforming.ts +++ b/test/openbadges/fixtures/obv3p0-openbadge-spec-conforming.ts @@ -1,11 +1,10 @@ /** * Spec-conforming VCDM v2 OB 3.0 OpenBadgeCredential fixture. * - * Designed to round-trip through every schema phase of the - * 2026-04-18-openbadges-recognizer-and-subchecks plan: every + * Designed to round-trip through every OB 3.0 class schema: every * required envelope, profile, achievement, subject, and result - * field is present so later phases can assert on the same fixture - * without amendment. + * field is present, so a spec for any one class can assert against + * this fixture without amending it. * * Authored as a `.ts` module (rather than `.json`) to match the * existing fixture convention in this repo diff --git a/test/openbadges/openbadges-submodule.spec.ts b/test/openbadges/openbadges-submodule.spec.ts index 1ab0c57..3378a06 100644 --- a/test/openbadges/openbadges-submodule.spec.ts +++ b/test/openbadges/openbadges-submodule.spec.ts @@ -7,7 +7,7 @@ * dynamic `import('@digitalcredentials/verifier-core/openbadges')` * catches typos in `exports` and missing re-exports in * `src/openbadges/index.ts`. - * 2. **Default-path invariant (Q2).** A vanilla + * 2. **Default-path invariant.** A vanilla * `createVerifier()` against a real OB credential emits zero * OB-related `CheckResult` entries. * 3. **Opt-in path emits OB findings.** Adding `openBadgesSuite` @@ -118,7 +118,7 @@ describe('@digitalcredentials/verifier-core/openbadges (integration)', () => { expect(ACHIEVEMENT_TYPE_EXT_PREFIX).toBe('ext:'); }); - it('runs no OB checks by default (Q2 invariant)', async () => { + it('runs no OB checks by default', async () => { const verifier = createVerifier(fakeVerified); const result = await verifier.verifyCredential({ credential: cloneCredential() diff --git a/test/openbadges/schema/achievement-subject-v3p0.spec.ts b/test/openbadges/schema/achievement-subject-v3p0.spec.ts index 01b75bb..0532b0c 100644 --- a/test/openbadges/schema/achievement-subject-v3p0.spec.ts +++ b/test/openbadges/schema/achievement-subject-v3p0.spec.ts @@ -1,5 +1,5 @@ /** - * Spec for the Phase-6 OB 3.0 `AchievementSubject` (§B.1.3) class + * Spec for the OB 3.0 `AchievementSubject` (§B.1.3) class * schema, its **id-or-identifier[]** at-least-one refinement, and * its backfill into the top-level envelope's `credentialSubject` * slot. @@ -119,7 +119,7 @@ describe('Obv3p0AchievementSubjectSchema (standalone)', () => { } }); - it('passes result[] entries through unchanged (Phase 7 backfills)', () => { + it('passes result[] entries through unchanged', () => { const opaqueResult = { type: ['Result'], value: '95', diff --git a/test/openbadges/schema/achievement-v3p0.spec.ts b/test/openbadges/schema/achievement-v3p0.spec.ts index 80b6df8..a4c6338 100644 --- a/test/openbadges/schema/achievement-v3p0.spec.ts +++ b/test/openbadges/schema/achievement-v3p0.spec.ts @@ -1,10 +1,10 @@ /** - * Spec for the Phase-5 OB 3.0 `Alignment` (§B.1.5) and - * `Achievement` (§B.1.1) class schemas. + * Spec for the OB 3.0 `Alignment` (§B.1.5) and `Achievement` + * (§B.1.1) class schemas. * - * Achievement is not yet wired into `AchievementSubject` (Phase 6 - * does that). These schemas are tested standalone via - * `safeParse`. Path-based `instance` assertions verify that + * These schemas are tested standalone via `safeParse`; the + * envelope spec covers them in composition. Path-based + * `instance` assertions verify that * malformed nested entries (e.g. `alignment[1]`) carry accurate * JSON Pointers when run through Zod. */ diff --git a/test/openbadges/schema/envelope-v3p0.spec.ts b/test/openbadges/schema/envelope-v3p0.spec.ts index f9ce68c..3fddb1b 100644 --- a/test/openbadges/schema/envelope-v3p0.spec.ts +++ b/test/openbadges/schema/envelope-v3p0.spec.ts @@ -1,8 +1,6 @@ /** - * Spec for the Phase-2 OB 3.0 envelope schema. - * - * Asserts the contract documented in - * `docs/plans/2026-04-18-openbadges-recognizer-and-subchecks/02-envelope-and-date-discriminator.md`. + * Spec for the OB 3.0 envelope schema, including the cross-field + * VCDM v1/v2 date discriminator (`issuanceDate` vs `validFrom`). */ import { describe, it, expect } from 'vitest'; @@ -219,9 +217,9 @@ describe('parseObv3p0OpenBadgeCredential (envelope)', () => { }); it('omits instance for whole-document problems (none expected at this layer)', () => { - // Sanity: every problem we currently raise has a path. Future - // changes that introduce root-level refines should leave - // `instance` undefined per the design's policy. + // Sanity: every problem we currently raise has a path. A + // root-level refine added later should leave `instance` + // undefined rather than point at the document root. const cred = clone(obv3p0OpenBadgeSpecConforming); cred.id = 'not-an-iri'; diff --git a/test/openbadges/schema/image-v3p0.spec.ts b/test/openbadges/schema/image-v3p0.spec.ts index 188bf79..1510bee 100644 --- a/test/openbadges/schema/image-v3p0.spec.ts +++ b/test/openbadges/schema/image-v3p0.spec.ts @@ -1,10 +1,6 @@ /** - * Spec for the Phase-3 OB 3.0 `Image` class schema and the - * `ImageField()` builder, plus its backfill into the top-level - * credential `image` slot. - * - * Asserts the contract documented in - * `docs/plans/2026-04-18-openbadges-recognizer-and-subchecks/03-image.md`. + * Spec for the OB 3.0 `Image` class schema and the `ImageField()` + * builder, plus its use in the top-level credential `image` slot. */ import { describe, it, expect } from 'vitest'; diff --git a/test/openbadges/schema/profile-v3p0.spec.ts b/test/openbadges/schema/profile-v3p0.spec.ts index b8eb322..6fba147 100644 --- a/test/openbadges/schema/profile-v3p0.spec.ts +++ b/test/openbadges/schema/profile-v3p0.spec.ts @@ -1,10 +1,7 @@ /** - * Spec for the Phase-4 OB 3.0 `Profile` class schema and the - * `ProfileRefField()` builder, plus its backfill into + * Spec for the OB 3.0 `Profile` class schema and the + * `ProfileRefField()` builder, plus its use in * `credential.issuer`. - * - * Asserts the contract documented in - * `docs/plans/2026-04-18-openbadges-recognizer-and-subchecks/04-profile-and-profileref.md`. */ import { describe, it, expect } from 'vitest'; @@ -154,7 +151,8 @@ describe('credential.issuer (backfilled ProfileRefField)', () => { // when neither branch advances past structural matching — the // string-IRI branch is rejected immediately (input is an object) // and the Profile branch fails at the missing required `type`. - // This matches the plan's "appropriate parent path" allowance. + // Attribution to the parent path is accurate enough here: the + // failure is "this issuer value fits neither form". assertMalformedAt(result, '/issuer'); }); diff --git a/test/openbadges/schema/result-v3p0.spec.ts b/test/openbadges/schema/result-v3p0.spec.ts index 00a0a0b..8bde445 100644 --- a/test/openbadges/schema/result-v3p0.spec.ts +++ b/test/openbadges/schema/result-v3p0.spec.ts @@ -1,5 +1,5 @@ /** - * Spec for the Phase-7 OB 3.0 result trio: + * Spec for the OB 3.0 result trio: * - RubricCriterionLevel (§B.1.18) * - ResultDescription (§B.1.17) * - Result (§B.1.16) @@ -10,9 +10,9 @@ * attribution for malformed nested entries when validated through * the envelope. * - * Phase 7 closes out the strict envelope; Phase 8 will refactor - * the existing OB semantic checks (achieved-level, result-ref) to - * consume this normalized form via `recognizedProfile`. + * The OB semantic checks (achieved-level, result-ref) parse with + * the tolerant shapes in `openbadges-zod.ts` instead of this + * normalized form, so nothing here constrains them. */ import { describe, it, expect } from 'vitest'; diff --git a/test/phase-filter.spec.ts b/test/phase-filter.spec.ts index ebbc2a3..260fdf1 100644 --- a/test/phase-filter.spec.ts +++ b/test/phase-filter.spec.ts @@ -1,6 +1,5 @@ /** - * Spec for the suite-phase filter (Phase 10 of the - * 2026-04-18-openbadges-recognizer-and-subchecks plan). + * Spec for the suite-phase filter. * * Coverage: * diff --git a/test/signed-bytes-preserved.spec.ts b/test/signed-bytes-preserved.spec.ts index c4a639a..363b2ac 100644 --- a/test/signed-bytes-preserved.spec.ts +++ b/test/signed-bytes-preserved.spec.ts @@ -32,8 +32,9 @@ const offlineHttpGetService = { const documentLoader = defaultDocumentLoaderFor(offlineHttpGetService as never); /** - * The issuer image `caption` is the load-bearing field: it is signed, and it is - * the key the old schema dropped. + * The issuer image `caption` is the load-bearing field: it is signed, and it + * is exactly the kind of key a nested object schema drops when it does not + * name it. */ const credentialTemplate = (issuerDid: string): Record => ({ '@context': [ @@ -128,9 +129,8 @@ describe('signed bytes survive verification', () => { }); it('returns the credential as issued, so a second pass can re-verify it', async () => { - // dcc-transaction-service re-verifies `verifiableCredential` in its - // asynchronous Open Badges pass. A rewritten copy fails there even though - // the synchronous pass succeeded. + // Consumers re-verify `verifiableCredential` in a later pass. A + // rewritten copy fails there even though the first pass succeeded. const result = await verifier().verifyCredential({ credential: signedCredential, phases: ['cryptographic'] diff --git a/test/verifier-cache-sharing.spec.ts b/test/verifier-cache-sharing.spec.ts index cfad642..5140198 100644 --- a/test/verifier-cache-sharing.spec.ts +++ b/test/verifier-cache-sharing.spec.ts @@ -1,5 +1,6 @@ /** - * Pins the headline performance claim of P-A: a single {@link createVerifier} + * Pins the verifier's headline performance claim: a single + * {@link createVerifier} * instance shares its long-lived caches across calls. In particular, an * issuer registry lookup that requires an HTTP fetch happens once per * registry URL — subsequent verifications for the same issuer hit the @@ -137,7 +138,7 @@ describe('Verifier cache sharing', () => { expect(http.callsTo(REGISTRY_URL)).toBe(2); }); - describe('status list URL routing through httpGetService (P-E)', () => { + describe('status list URL routing through httpGetService', () => { const STATUS_LIST_URL = 'https://example.test/status/list-pe'; /** Same-shape VC as `makeCredential`, but adds a BitstringStatusListEntry. */ @@ -173,13 +174,13 @@ describe('Verifier cache sharing', () => { credential: makeRevocableCredential('urn:uuid:status-once-1') }); - // Pre-P-E this was 2 (DataIntegrityCryptoService.checkStatus + statusSuite). - // Post-P-E only statusSuite fetches the list. + // Only statusSuite fetches the list: the proof path passes a no-op + // `checkStatus`, so there is no second fetch from there. expect(http.callsTo(STATUS_LIST_URL)).toBe(1); }); }); - describe('document loader memoization per HttpGetService (P-E)', () => { + describe('document loader memoization per HttpGetService', () => { it('returns the same DocumentLoader for the same HttpGetService instance', () => { const fake: HttpGetService = FakeHttpGetService({}); const a = defaultDocumentLoaderFor(fake); diff --git a/test/verifier-recognition.spec.ts b/test/verifier-recognition.spec.ts index f5714c1..86b4dd6 100644 --- a/test/verifier-recognition.spec.ts +++ b/test/verifier-recognition.spec.ts @@ -1,8 +1,7 @@ /** * End-to-end spec for the recognition pipeline through `createVerifier`. * - * Asserts the contract documented in - * `docs/plans/2026-04-18-openbadges-recognizer-and-subchecks/00-design.md`: + * Asserts the recognition contract: * * - With no `recognizers` configured, `recognition.profile` emits * skipped and the result has no `normalizedVerifiableCredential` diff --git a/test/verify-credential.spec.ts b/test/verify-credential.spec.ts index 266ee00..17393de 100644 --- a/test/verify-credential.spec.ts +++ b/test/verify-credential.spec.ts @@ -535,13 +535,13 @@ describe('verifyCredential', () => { }); }); - // P-E regression: revoked credentials must fail with the failure - // sourced from the status suite, not the proof suite. We exercise this - // through the same code path createVerifier uses (defaultSuites + - // runSuites + hasFatalFailures). FakeCryptoService verifies both the - // credential proof and the unsigned factory status-list credential so - // the contract being pinned is the aggregation, not real cryptography. - describe('revoked credential sourcing (P-E)', () => { + // Revoked credentials must fail with the failure sourced from the + // status suite, not the proof suite. We exercise this through the same + // code path createVerifier uses (defaultSuites + runSuites + + // hasFatalFailures). FakeCryptoService verifies both the credential + // proof and the unsigned factory status-list credential so the contract + // being pinned is the aggregation, not real cryptography. + describe('revoked credential sourcing', () => { it('flips verified to false via status.bitstring (not proof.signature) when status list marks the index revoked', async () => { const listUrl = 'https://factory.test/status/list-revoked-pe'; const slCred = await StatusListCredentialFactory({