<!-- SPDX-License-Identifier: Apache-2.0 -->
# PSDP Website Claims Ledger

**Status:** authoritative · built 2026-06-12 · branch `feat/marketing-site`
**Rule:** every factual claim on any page of this site MUST be one of the allowed
phrasings below (verbatim or a strict weakening — never a strengthening). Numbers,
dates and qualifiers are load-bearing. Adversarial reviewers enforce by diff against
this file. If a claim you want is not here, it does not go on the site.

Seeded from `docs/PQ_ZK_STATUS.md` (the DO-NOT-CLAIM register — its prohibitions are
absolute) and verified read-only against the artifacts in the main tree at
`/home/kaali/crazy idea/` (gitignored/untracked there; paths below are relative to
that tree unless absolute).

---

## 1. Allowed claims

### OIDF conformance

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| OIDF-1 | "Tested against the official OpenID Foundation conformance suite (local run, reproducible): **319 of 332 test modules passed, 0 failed** (11 warnings, 2 skipped), across OID4VCI, OID4VP and OpenID Federation test plans. Self-run evidence — **not** an OpenID Foundation certification." | `tier3_results/oidf_conformance/SCORECARD.json` (run `local_oidf_20260601_164224_subprojectA_STRICT`); independently reproduced by `tier3_results/local_oidf_freshrun_20260604_161726_full/summary.json` | Parsed both JSONs: total_modules 332, passed 319, failed 0, warning 11, other/skipped 2 in BOTH the 2026-06-01 and 2026-06-04 runs. Plan breakdown: OID4VCI issuer 112 + issuer-HAIP 126, OID4VP verifier/wallet (1final + id3 + HAIP) 89, OpenID Federation deployed-entity 5/5. |
| OIDF-2 | "The **FAPI 2.0 Security Profile (Final)** test modules embedded in the OID4VCI issuer plans: **78 of 80 passed, 0 failed** (2 warnings) — local run of the official OIDF suite." | `tier3_results/local_oidf_20260526_122209_mdoc_keyatt/summary.json`; re-confirmed in `tier3_results/local_oidf_freshrun_20260604_161726_full/summary.json` | Tallied modules whose names match `fapi2-security-profile-final-*`: 2026-05-26 run = 80 modules, 78 PASSED (76 FINISHED + 2 MANUAL_REVIEW_OK), 2 WARNING, 0 FAILED; 2026-06-04 run = identical 78/2/0. |
| OIDF-3 | "An earlier full-suite baseline (2026-05-26): **210 of 218 modules passed, 0 failed** (7 warnings, 1 skipped)." | `tier3_results/local_oidf_20260526_122209_mdoc_keyatt/summary.json` | Tallied all modules: 218 total; 210 PASSED (201 FINISHED + 9 MANUAL_REVIEW_OK), 7 WARNING, 1 SKIPPED, 0 FAILED. |
| OIDF-4 | "Every conformance result bundle is signed by the suite's own key and archived; runs are reproducible. We are **not** listed on openid.net/certification." | `conformance_results/test-log-*.zip` (signed suite exports); `tier3_results/oidf_conformance/SCORECARD.json` | Signed test-log zips exist on disk. No published-certification artifact exists anywhere; the suite-internal `"certified": 330` JSON field is a suite status counter, NOT a certification (see Rejected R-3). |

### Wallet interoperability

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| WAL-1 | "**22 credential types, in 43 issuer configurations** (SD-JWT VC and ISO mdoc formats), each issued **and stored** end-to-end against the walt.id wallet stack over live OID4VCI." | `auto_research/scripts/wallet_baselines.json` (`wallets.walt_id.flow_status.oid4vci.per_doctype`); per-doctype `evidence_dir` pointers into `tier3_results/`; run reports in `auto_research/reports/wallet_wire/2026-05-2*/` | Parsed baseline: 43 per-doctype entries, **all 43** status `live_wire_pass_with_storage`; 22 distinct credential-type bases (PID, mDL, EHIC, diploma, civil-status ×3, vaccination, tax, payment, etc.). Do NOT say "23 types" (see Rejected R-5). |
| WAL-2 | "Austria's **vck** library (A-SIT / the engine behind the ID-Austria Valera wallet) **presented an SD-JWT PID to the PSDP verifier over OID4VP 1.0 Final** (direct_post + DCQL); PSDP verified the issuer signature and the Key-Binding JWT and accepted (2026-06-08, reproducible harness)." | `scripts/vck-vp-holder/README.md` (run transcript + rerun instructions); harness source in `scripts/vck-vp-holder/src/` | README records the full 4-leg flow with HTTP 200 trail and `RESULT: PASS … (KB-JWT verified)`; harness is rerunnable. Scope limits MUST hold: it was the vck **library**, not the Valera app binary; dc+sd-jwt only, not JARM/mso_mdoc. |
| WAL-3 | "Tested against **real Android wallet apps on physical devices** (Paradym, Sphereon, EUDI reference, Procivis), with captured device logs (adb/logcat) against live PSDP endpoints." | `tier3_results/android_config_sweep_*` (paradym/sphereon/eudi_ref/procivis dirs with logcat captures); `tier3_results/wallet_run_analysis_2026042*_paradym_*.json` (okhttp/4.12.0 device traffic + ReactNativeJS logcat) | Run-analysis JSONs show real-device user agents and captured logcat from `id.paradym.wallet` resolving PSDP deep links over a live tunnel. Do NOT claim a completed end-to-end real-phone flow (see Rejected R-6). |
| WAL-4 | "PSDP's live issuer has served **complete OID4VCI issuance flows** (metadata → offer → token → credential, all HTTP 200) to wallet clients identifying as Paradym/Credo, Sphereon and Lissi (logged 2026-06-04)." | `tier3_results/wallet_attempts/attempts.jsonl` | Parsed JSONL for 2026-06-04: full 200-status chains for UAs `Paradym/1.0`, `Credo TS / Paradym (OpenWallet Foundation Labs)`, `Sphereon Wallet`, `Sphereon/1.0`, `Lissi/2.0`. Phrasing "identifying as" is mandatory — the log records user agents, not proof of the physical app. |
| WALLET-1 | "Try it with your own EUDI wallet runs the **standard OID4VCI issuance + OID4VP presentation flow (selective disclosure)**, by QR: the live PSDP **demo issuer** mints an EU PID (SD-JWT VC, demo data) over OID4VCI, and the verifier checks the **issuer signature** and the wallet's **Key-Binding JWT** and accepts a presentation that discloses **only `age_over_18`** — no date of birth. This is a **per-presentation check with holder key-binding, NOT the cross-relying-party-unlinkable PSDP proof.** Issuance has completed end to end (metadata → offer → token → credential, all HTTP 200) for clients identifying as **Sphereon** and **Lissi**, and an SD-JWT PID presentation has been verified from the **vck** library. **Paradym** and the stock **EUDI Reference Wallet** currently reject PSDP demo credentials at the **trust-anchor step (our demo issuer certificate is not in their built-in trust list)** — not a flaw in the proof." | live keyless routes `POST /api/v1/oid4vci/offer` (returns `openid-credential-offer://?credential_offer_uri=…`), `POST /api/v1/oid4vp/initiate?credential_format=dc+sd-jwt&response_mode=direct_post&claims_override=age_over_18` (returns `openid4vp://…` + `state`), `GET /api/v1/oid4vp/result/{state}` (the SD-JWT verify path in `demo/api.py` returns `status:"accepted"` only after issuer-sig + KB-JWT verification); evidence reuses WAL-1 (issued+stored vs walt.id), WAL-2 (vck presented + verified), WAL-3 (real Android apps incl. EUDI-ref/Paradym), WAL-4 (Sphereon/Lissi-identifying full OID4VCI); EUDI-ref trust-list dependency is coded in `demo/api.py` (`_EUDI_REF_FAMILY` warning, ~L1674) | Ran all three routes live with `PSDP_PRODUCTION=1` (port 8120, no `X-API-Key`): offer returned a real `openid-credential-offer://` URL that rebases to the public host under `X-Forwarded-Host`; initiate returned a real `openid4vp://` request with `claims_override:["age_over_18"]` + `state`; result returned `202 {"status":"pending"}`. The qualifiers are mandatory and carry the existing WAL-* limits: "identifying as" (UA strings, not the physical app, per WAL-4/R-6), the vck **library** not the Valera binary (WAL-2/R-7), and per-presentation ≠ unlinkable (FV-3). Ceiling = SEC-3; never pair with any post-quantum claim (the ZK/unlinkable path is separate, AGE-ZK-1/FV-3). |

### Formal verification

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| FV-1 | "**33 protocol theorems tracked: 15 discharged (machine-checked), 18 partial.** We publish the split — partial means exactly that." | `auto_research/knowledge/theorem_map.json` (generated 2026-05-29) | Counted `health` field across all 33 entries: `discharged` = 15, `partial` = 18, unverified = 0. |
| FV-2 | "Protocol model machine-checked in the **Tamarin prover** (Dolev-Yao adversary): **36 lemmas verified — 32 trace properties + 4 observational-equivalence proofs** (≈5,500 proof steps), across 7 theory files." | `conformance_results/EVIDENCE_PACKAGE.json` (`formal_verification` block); `.spthy` sources in `conformance_results/*.spthy` | Read JSON: tool "Tamarin Prover 1.12.0", total_verified 36 (32 trace + 4 equivalence), total_proof_steps 5517, 7 theories; the 7 `.spthy` files exist beside it. |
| FV-3 | "**Designed and machine-checked for unlinkability properties:** identifier-hiding (T10a), audit/verifier-split (T10d) and nullifier-unlinkability / private-presentation theorems are **discharged**; verifier-view unlinkability, issuer hiding and selective-disclosure privacy are modeled with machine-verified observational-equivalence lemmas but remain **partial** overall." | `auto_research/knowledge/theorem_map.json` (entries `theorem10a`, `theorem10d`, `private_presentation`, `theorem10`, `issuer_hiding`, `selective_disclosure_privacy`) | Per-entry check: theorem10a/theorem10d/private_presentation `health=discharged` (private_presentation: 10/10 lemmas verified); theorem10, issuer_hiding, selective_disclosure_privacy have all Tamarin lemmas `verified` but `health=partial`. NEVER "unlinkability-certified". |
| FV-4 | "Reference ZK circuits additionally checked with **Picus (Veridise, Z3)** — 'properly constrained' — and **Circomspect (Trail of Bits)** — 'no issues found' — plus 14/14 negative-witness tests (circom reference circuits, April 2026 evidence package)." | `conformance_results/EVIDENCE_PACKAGE.json` (`circuit_formal_verification` block) | Read JSON verbatim. Scope qualifier "(circom reference circuits)" is mandatory — the live verify path is the arkworks Groth16 backend, not these circom artifacts. |

### Post-quantum cryptography (limitation statement is MANDATORY wherever PQC appears)

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| PQ-1 | "**PQC-hybrid authentication (ML-DSA-65) + confidentiality (ML-KEM-768). The zero-knowledge layer is classical Groth16/BN254**, with a de-risked (not yet live, not yet formally sound) transparent PQ-STARK backend on the roadmap." | `docs/PQ_ZK_STATUS.md` §2, §6 ("the ONLY truthful posture") | This is the register's own ceiling sentence, quoted. Anything stronger is an overclaim. The second sentence may not be dropped when the first is used. |
| PQ-2 | "Hybrid post-quantum key establishment (**X25519 + ML-KEM-768**) and hybrid issuer signatures (**ES256/EdDSA + ML-DSA-65, FIPS 204**) are implemented in the reference stack." | `src/psdp_ref/pqc_credentials.py` (e.g. `"hybrid_id": "mlkem768-x25519-hybrid"`, `"kem_scheme": "X25519 + ML-KEM-768"`); `src/psdp_ref/issuer_signatures.py`; `docs/PQ_ZK_STATUS.md` §2 | Grepped worktree code: hybrid KEM and ML-DSA-65 identifiers present in live modules; PQ_ZK_STATUS independently confirms both layers as "real, hybrid". Must appear together with the PQ-1 ZK limitation. |
| PQ-3 | "A transparent **PQ-STARK proving spike** (hash-based, no trusted setup) is de-risked: fused proof ≈73 KB, prove time ≈16 s on 2016 laptop hardware — **not live, not formally sound, not production-ready**; the shipping ZK backend remains Groth16/BN254." | `docs/PQ_ZK_STATUS.md` §3–4 | Numbers and the triple negative are quoted from the register's "best-branch maturity" section. The three "not"s are mandatory. Never quote sub-KB "PQ" proof sizes (§5). |

### Security posture

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| SEC-1 | "In production mode (`PSDP_PRODUCTION=1`) the verifier **refuses mock proofs** — submissions are rejected with `mock_crypto_rejected_in_production` — and startup checks enforce production configuration before serving." | `src/psdp_ref/age_verification.py` (allow_mock_crypto default = `not _is_production()`, lines ~171–211); `src/psdp_ref/startup_checks.py` (PSDP_PRODUCTION gates, lines ~182–236) | Read code in this worktree (master `fe33296` lineage): default mock acceptance is inverted by the production flag; startup_checks hard-enforces at boot. |
| SEC-2 | "**Real Groth16/BN254 verification is exercised in CI:** a blocking job builds the Rust provers and runs the suite with `PSDP_REQUIRE_ZK=1`, so a missing prover is a hard failure, not a silent skip." | `.github/workflows/ci.yml` (`zk-research` job); `tests/test_zk_prover_integrity.py`; `scripts/check_zk_prover_integrity.py` | Read the workflow: builds `prover/arkworks` + `prover/bbs` with `--locked`, runs integrity check `--require` and the research suites under `PSDP_REQUIRE_ZK=1`. |
| SEC-3 | "PSDP is a **reference implementation; production hardening is in progress.**" (This is the CEILING — no stronger readiness wording anywhere.) | 2026-06-09 production-readiness audit (`~/.psdp_research/PRODUCTION_READINESS_AUDIT_2026-06-09.md`) + remediation re-verified 2026-06-12 | Audit scored master 27/100 with remediation since landed (mock-crypto, admin/SSRF, CI-real-ZK fixed; revocation partial; license + ephemeral keys open). "Production-ready" is therefore false; this sentence is the honest maximum. |
| SEC-4 | "Production-mode issuer trust is **fail-closed** (real Ed25519 issuer-signature verification, expiry, registry/revocation status — any failure rejects) and one-time-use nullifiers persist across restarts via a file-backed replay guard." | `src/psdp_ref/production.py` (require_trusted_issuer, ProductionNullifierLedger) | Read module: docstring + code implement exactly this, scoped to the production layer. Do NOT generalize to "fail-closed by default everywhere" — e.g. `require_active_status` still defaults False in demo policies (Rejected R-8). |
| LIC-1 | "Source-available: protocol-core modules carry **Business Source License 1.1** headers (not open source — license finalization in progress); commodity adapters (e.g. the OIDC login adapter) are **Apache-2.0**." | `src/psdp_ref/production.py` header (BUSL-1.1, "NOT open source"); `src/psdp_ref/transport/oidc_rp/__init__.py` header (Apache-2.0); repo-root `LICENSING_DECISIONS.md` | Read SPDX headers in this worktree. The BSL legal text is unfinalized (no licensor entity), so the parenthetical is mandatory and blanket "open source" is banned (R-9). |

### Age verification (real zero-knowledge)

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| AGE-ZK-1 | "The public `POST /verify/age` endpoint mints and verifies a **real Groth16/BN254 zero-knowledge proof** end to end: the holder proves `age_over_18` (and a per-relying-party scoped nullifier) in zero knowledge against a **trusted issuer accumulator** (a fixed shared set of **K = 64** members), **revealing no date of birth** — the holder's secret is **CSPRNG-random, not derived from the birthdate**, so the DOB is **not recoverable** from the public transcript or the published root. It is **reference-grade**: the proving system uses a **single-party deterministic trusted setup — not a multi-party ceremony** — so it is not production-secure against a determined forger, and the **64-member anonymity set is a reference scale, not internet-scale**. A real proof takes **about 10–15 seconds** — the honest cost of real ZK." | `src/psdp_ref/age_verification.py` (`make_demo_age_presentation` mints a real `psdp-arkworks-present` Groth16 transcript; `_verify_real_age_presentation` verifies fail-closed — pins the trusted issuer accumulator root, the canonical verifying key, and the requested `min_age`); `src/psdp_ref/age_issuer.py` (`K_ANONYMITY = 64`; `generate_holder_secret` = CSPRNG, NOT `SHA-256(born)`; one published `trusted_age_root` shared by every holder); the per-call re-randomized proof seed in `src/psdp_ref/unlinkable_presentation.py`; `tests/test_age_real_zk.py` (12-case acceptance matrix), `tests/test_unlinkability_fixed.py` (DOB-not-recoverable, K≥64) | Read code in this worktree (fix commit `72d53ec` rewrote the issuer to a shared K=64 accumulator with CSPRNG member secrets, so the old `root = f(born)` DOB leak is gone): the real-ZK path routes on `_ZK_PRESENTATION_KEY`, runs the Groth16 prover, and rejects mock proofs in production. `test_age_real_zk.py` asserts an over-18 adult ACCEPTS and that nothing else forges an over-18 ACCEPT (underage, tampered proof bytes, forged/self-built root, foreign verifying key, mock proof, missing/malformed transcript, wrong-threshold proof). `test_unlinkability_fixed.py` adds `test_dob_not_recoverable_from_root` (the published root is identical across a spread of DOBs and member secrets are not `SHA-256(born)`) and `test_k_anonymity_set_size` (≥ 64). Re-verified by an independent red-team (all_zk_breaks_closed, 44/44 acceptance tests) and re-run in this worktree (the two closure suites: 14 passed in ~39 s, real Groth16 proofs). The "reference-grade / single-party deterministic setup / 64-member set" qualifiers and the SEC-3 ceiling are MANDATORY — never "production-secure age verification", "internet-scale", or any wording implying a ceremony. The ZK layer is still classical Groth16/BN254, so DO-NOT-CLAIM #1 applies: never pair this with any post-quantum claim. |

### Cross-relying-party unlinkability (real zero-knowledge)

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| UNLINK-1 | "The **same** holder presenting an age proof to **two different relying parties** (`POST /verify/age`, same date of birth, different `relying_party_id`) is **accepted at both** (`over_age: true`) yet produces **two different per-relying-party nullifiers**, and each show is a **freshly re-randomized** proof (byte-different every time) — so two **colluding** sites have nothing stable to correlate on. Every holder shares **one published issuer accumulator root** (a fixed set of **K = 64** CSPRNG-random members): the root is an **issuer-level constant identical for everyone**, NOT a per-person handle, and because member secrets are random (not derived from the birthdate) the **date of birth is not recoverable** from the root or transcript. Issuance is **blinded** — the demo issuer admits only the commitment, never the holder secret, so it cannot link a presentation to a holder; and the personhood path (`/uniqueness/verify`) publishes a **per-scope blinded commitment** so the same human shows DIFFERENT commitments at different sites. Each proof reveals only **{shared issuer root, site-scoped nullifier, age ≥ 18}**; a **fail-closed replay guard** rejects a captured transcript (`replay_detected`). Each is a **real Groth16/BN254** zero-knowledge proof (~10–15 s per site). **Reference-grade, honest residuals stated:** unlinkable **within a 64-member anonymity set** (a reference scale, **not internet-scale**); a **single-party deterministic trusted setup — not a multi-party ceremony**; the replay guard and issuer/wallet co-location are **demo-grade (in-memory)**; `/uniqueness/verify` relies on clients using the **canonical blinded builder** (not yet server-enforced); two holders of the **exact same age map to the same set member** (an over-merge within one site that never de-anonymizes). The unlinkability property itself is **modeled and machine-checked but partial overall** (see FV-3) — never 'unlinkability-certified'. Ceiling = SEC-3." | `src/psdp_ref/age_issuer.py` (`K_ANONYMITY = 64`; shared `trusted_age_root`; `generate_holder_secret` = CSPRNG; `admit_commitment` = blinded — no `secret`/`salt` param; `issuer_state` holds only `{commitment, age, leaf_index}` + root); `src/psdp_ref/unlinkable_presentation.py` (`present_from_accumulator` draws a fresh per-call `proof_seed` → re-randomized `proof_base64`); `src/psdp_ref/age_verification.py` (RP-scoped nullifier via `age_issuer.rp_scope_for`; fail-closed replay guard → `replay_detected`); `src/psdp_ref/proof_of_uniqueness.py` (`build_uniqueness_zk_request` proves a per-scope blinded secret `s_ctx = H(domain‖secret‖scope)`); `tests/test_unlinkability_fixed.py` + `tests/test_uniqueness_unlinkable.py` | Read code in this worktree; fix commit `72d53ec` closed the 4 audit breaks (root/DOB leak, proof re-randomization+replay, issuer-hiding, uniqueness correlator) and an independent red-team re-verified (all_zk_breaks_closed, 44/44 acceptance tests). `test_unlinkability_fixed.py` asserts: same holder + two RPs → SAME root, DIFFERENT nullifiers, DIFFERENT (re-randomized) proof bytes; root IDENTICAL across different DOBs (carries no per-user info); DOB not recoverable; K ≥ 64; blinded issuance keeps the secret out of `issuer_state`; a replayed transcript is rejected (`replay_detected`); a minor still fails the predicate. `test_uniqueness_unlinkable.py` asserts the per-scope commitment DIFFERS for the same human across two RPs while staying a stable within-scope sybil key. Ran both suites in this worktree against the built `psdp-arkworks-present`/`-biometric-match` provers: **14 passed in ~39 s** (real Groth16 proofs throughout). Builds on AGE-ZK-1 (same route, same reference-grade + ~10–15 s + K=64 limits) and FV-3 (the unlinkability theorem split is partial). The ZK layer is classical Groth16/BN254, so DO-NOT-CLAIM #1 applies: NEVER pair with any post-quantum claim, NEVER claim "internet-scale" anonymity, and NEVER write "unlinkability-certified" / "proven unlinkable in production" (DO-NOT-CLAIM #8 → FV-3 only). |

### Multi-factor step-up (real WebAuthn passkey)

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| MFA-1 | "Real WebAuthn passkey step-up: a federated login (**AAL1**) is upgraded to passkey-bound **AAL2** via a real **ECDSA-P256** assertion + a **same-holder possession proof**; the upgraded credential is **Ed25519-signed** and carries faithful multi-axis assurance (**IAL/AAL/FAL**); the **demo IdP is simulated**. A passkey raises the authenticator axis (AAL) only — it does no identity proofing, so identity claims stay denied. Faithful labelling, not a certification or production-secure claim (ceiling = SEC-3)." | `demo/onramp.py` (`/onramp/login`, `/onramp/binding/challenge`, `/onramp/passkey/enroll`, `/onramp/stepup`); `src/psdp_ref/assurance_gateway/stepup.py` (`step_up`: passkey ⇒ AAL only, fail-closed on a non-same-holder factor); `src/psdp_ref/passkey_binding.py` (`parse_registration`, `verify_assertion` = ECDSA-P256-SHA256); `src/psdp_ref/assurance_gateway/holder_binding.py` (`bind_same_holder` = WebAuthn possession + scoped nullifier); `tests/test_onramp_stepup_linking.py`; `tests/assurance/test_stepup.py`; `tests/test_site_mfa_demo.py` | Read code in this worktree: `/onramp/stepup` re-runs the gateway against the SAME verified login behind a load-bearing same-holder gate (`_binding_proof` → `bind_same_holder`), a passkey factor maps to the AAL axis (`_FACTOR_AXIS[PASSKEY] = "AAL"`), and the upgraded credential is re-verified with `verify_assured_credential(..., allow_mock=False)` (a real Ed25519 signature check). `tests/test_site_mfa_demo.py` drives the 4 open endpoints with the pure-software WebAuthn authenticator (real `create()` + real `get()` assertion): login → enroll → stepup yields `assurance.aal == 2` while `ial` stays `1`, the credential's `signature_suite` is `REAL_SIGNATURE_SUITE` and `verified is True`, and a foreign authenticator's assertion is rejected `400` (same-holder gate). The demo IdP is the same simulated provider as the "Bring your own login" card — RS256 id_token real, identities not. NEVER pair with any "production-secure", "certified" or post-quantum wording. |

### AI-agent delegation (key-bound metered spend caps)

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| AGENT-1 | "An AI-agent delegation can carry a **metered spend cap**, enforced live. A **key-bound, attenuating** delegation chain (principal → team-lead → worker) issues the worker a **tighter cap (€40)** than its parent (€100); two metered `purchase.execute` attempts of €30 each are decided in front of you — the **first is authorized** (€30 ≤ €40), the **second is denied `cap_exceeded`** (running total €60 > €40), enforced **at action time, not advisory**. Each sub-delegation is **cryptographically signed by its parent** (attenuating, accountable), and **every attempt — authorized and denied — is recorded in an auditor-only sealed audit record**. Cap enforcement is real; the credentials are **demo-issued with ephemeral keys** (reference-grade, ceiling = SEC-3)." | `GET /agent/chain/demo` in `src/psdp_ref/agent_identity.py` (`issue_agent_delegation` with `limits={'spend':{'max':…,'currency':'EUR'}}` and `parent_credential`/`parent_sk` key-binding; `verify_chain`; `agent_act(..., consumption_ledger=ledger)`); `src/psdp_ref/delegation_caps.py` (`SqliteConsumptionLedger.reserve` — atomic per-ancestor debit, returns `cap_exceeded`); `src/psdp_ref/delegation.py` (parent-signed, attenuated sub-delegations) | Ran `GET /agent/chain/demo` live with `PSDP_PRODUCTION=1` (port 8121, no `X-API-Key`): `chain` = team-lead `limits.spend.max=100 EUR` → worker `limits.spend.max=40 EUR`; `attempts[0]` = spend 30, `authorized: true`, `decision: "authorized"`, `reasons: []`; `attempts[1]` = spend 30, `authorized: false`, `decision: "denied"`, `reasons: ["cap_exceeded"]`, `cap_reservation.allowed: false`. Each attempt carried an `audit_header` (`record_type: escrowed-audit-record`). NOTE: the audit envelope internally uses ML-KEM/X25519, but that is deliberately NOT surfaced on the page — surfacing it would trigger the mandatory PQ-1 limitation sentence (DO-NOT-CLAIM #1/#3), so AGENT-1 stays scoped to caps + parent-signing + auditor-only sealing and makes NO post-quantum claim. Ceiling = SEC-3; demo-issued ephemeral keys, never "production-secure". |

### Company / posture

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| ORG-1 | "PSDP is a **pre-incorporation, single-founder project**. No legal entity exists yet; engagements are scoped accordingly. Honesty is the brand." | Design spec `docs/superpowers/specs/2026-06-12-marketing-site-redesign-design.md` §1; owner statement | Stated by the owner; no artifact contradicts. Any wording implying an existing company, customers, team, or revenue is banned. |

### Pricing (every figure MUST carry the word "indicative")

| id | allowed phrasing | source artifact | verified-how |
|---|---|---|---|
| PRC-1 | "Indicative: age-verification checks at **~$0.01–0.03 per check** at volume." | `products/VERIFIED_LOGIN_PRICING.md` (line ~56); `products/PRODUCTS_AND_PRICING_STATUS.md` scorecard | Both docs state $0.01–0.03 per age-predicate check. |
| PRC-2 | "Indicative: pre-certification / conformance audit engagements **€20–40k**; unlinkability / privacy-conformance audit **€30–60k**." | `/home/kaali/psdp-site/products/REVENUE_MODEL_12MO.md` (revenue-lines table); `products/PRODUCTS_AND_PRICING_STATUS.md` | Ranges read verbatim from the revenue model; STATUS doc carries compatible tiered bands ($10–75k). |
| PRC-3 | "Indicative: managed OIDF certification run **$2–5k per spec** (anchored on OIDF self-certification fees of $700–$3,500 per spec)." | `/home/kaali/psdp-site/products/REVENUE_MODEL_12MO.md`; `products/VERIFIED_LOGIN_PRICING.md` (line ~62) | Both figures present in the docs; the anchor is the public OIDF fee the docs cite. |
| PRC-4 | "Indicative: Conformance Coach subscription tiers **$199 / $799 / $2,000 per month**." | `products/PRODUCTS_AND_PRICING_STATUS.md` (2026-06-06 scorecard) | Read from the newer STATUS doc (supersedes the $99–499 row in REVENUE_MODEL_12MO.md — do not mix the two). |
| PRC-5 | "Measured (2026-06-06, developer hardware): a real Groth16/BN254 verification costs **~7–22 ms** of compute — on the order of **10–20 cents per million verifications** — so verification compute is not a pricing floor." | `products/VERIFIED_LOGIN_PRICING.md` (measured table, lines ~71–101); `products/PRODUCTS_AND_PRICING_STATUS.md` | Measured-bench tables read directly (21.8 ms loaded / 6.8 ms isolated; ~8–21¢/million). Qualifiers "measured, developer hardware" are mandatory. |

---

## 2. DO-NOT-CLAIM (absolute — from `docs/PQ_ZK_STATUS.md` plus audit rules)

Never state, write, demo, or imply ANY of the following, on any page, in any tense:

1. ❌ "PSDP has post-quantum zero-knowledge proofs" / "quantum-resistant ZK / unlinkability / predicates". The live ZK layer is Groth16/BN254 (Shor-breakable). (`docs/PQ_ZK_STATUS.md` §1, §6)
2. ❌ Any "256-byte PQ ZK" or other sub-kilobyte "post-quantum" proof-size figure — that is a Groth16-wrapped proof and NOT post-quantum. Honest PQ-STARK size ≈73 KB. (§5)
3. ❌ "In-circuit PQ issuer-signature verification" as a shipped product property — it is a spike on another branch. (§6)
4. ❌ "Certified", "conformance-signed", "OIDF-certified", "officially certified" or any phrasing implying a published OpenID Foundation certification. There is none; we are not on openid.net/certification. Allowed framing is OIDF-1/OIDF-4 only.
5. ❌ Any eIDAS legal-status claim (qualified/notified/recognized wallet or trust-service status). All PKI/trust-list/EUDI work is **interop evidence, not legal status**.
6. ❌ Entity-implying claims: "our company", "our customers", "our team", revenue, headcount. See ORG-1 for the honest posture.
7. ❌ "Production-ready", "battle-tested", "enterprise-grade". Ceiling = SEC-3.
8. ❌ "Unlinkability-certified" or "proven unlinkable in production". Allowed framing is FV-3 only.
9. ❌ Blanket "open source". Core modules are BUSL-1.1 ("NOT open source" per their own headers) and the BSL text is unfinalized. Allowed framing is LIC-1.
10. ❌ "All security findings fixed" / "0 known vulnerabilities" (the EVIDENCE_PACKAGE `security_audit: 117/117 fixed` block is superseded by the 2026-06-09 production-readiness audit, which found new criticals since remediated only in part).
11. ❌ Any number that does not appear, with source and verification note, in §1 of this ledger.

---

## 3. Rejected during audit (appendix)

| ref | candidate claim | why rejected |
|---|---|---|
| R-1 | "212 SUCCESS assertions" (old site, dynamically rendered counter) | Untraceable. The string "212" appears in no conformance artifact (`SCORECARD.json`, `EVIDENCE_PACKAGE.json`, run summaries). The old `reference-edition.html` rendered `oc.success` from a live endpoint; no artifact pins 212. |
| R-2 | "1,434" (old site figure, presumed function/assertion count) | Untraceable. Appears in no artifact searched (EVIDENCE_PACKAGE, scorecards, site sources — the only greppable "1,4xx" hits were CSS rgba values). |
| R-3 | "conformance-signed" / "330 of 332 certified" (old `reference-edition.html` ~line 657) | The `"certified": 330` field in `SCORECARD.json` is the suite's internal status counter, not a certification. Wording violates DO-NOT-CLAIM #4. Replace with OIDF-1 + OIDF-4. |
| R-4 | "FAPI 2.0 conformance-tested: 78/80, **certified**" | The 78/80 figure is verified (OIDF-2) but only as a local self-run; any "certified" framing rejected per #4. |
| R-5 | "23 credential types wire-verified against walt.id" | Artifact shows **22** distinct credential types (43 configurations, all passing). The 23rd (biometric-bound doctype) is not in `wallet_baselines.json`. Rounded down to WAL-1. |
| R-6 | "Paradym walked the full OID4VCI flow live on a real Android phone (2026-06-04)" | No on-disk artifact found for the 2026-06-04 real-phone session (searched `tier3_results/`, `auto_research/reports/`, `~/.psdp_research/`, home dir by mtime). `attempts.jsonl` shows complete flows only from clients *identifying* as Paradym (lab-harness UA strings); April real-phone artifacts (okhttp + logcat) show OID4VP request-fetch without completion. Weakened to WAL-3 + WAL-4. |
| R-7 | "OID4VP presentation received live from the vck **wallet app** (Valera)" | Harness tested the vck **library**, not the Valera app binary (per the harness README itself). Weakened to WAL-2 with mandatory scope limits. |
| R-8 | "Fail-closed defaults" (blanket) | Contradicted in part: `require_active_status` (revocation enforcement) still defaults to `False` in demo policy paths (`src/psdp_ref/age_verification.py:585`); the 2026-06-12 re-verification records revocation as "partial". Scoped to SEC-1/SEC-4. |
| R-9 | "Open source" (blanket) | Core files carry `BUSL-1.1 … NOT open source` headers; BSL legal text unfinalized (no licensor entity). Scoped to LIC-1. |
| R-10 | "117/117 security-audit findings fixed; 0 remaining" (`EVIDENCE_PACKAGE.json` security_audit block) | Superseded/contradicted by the 2026-06-09 read-only production-readiness audit (6 criticals found later; remediation landed 2026-06-10 but license + ephemeral-key issues remain). Using the April figure would be materially misleading. |
| R-11 | "902/902 unit tests pass" (EVIDENCE_PACKAGE) | Stale April 2026 snapshot of a different suite size; current suite is far larger and was not re-run as part of this audit. No test-count claim is allowed until re-measured at publish time. |
| R-12 | "$1B opportunity" / TAM-share claims | Revenue model itself labels year-1 coverage "a rounding error" and the $1B framing a moonshot. Only the indicative price points (PRC-1..5) are allowed; no market-size claims. |
| R-13 | "EBSI / eIDAS / trusted-list recognition" | All EBSI/PKI/LOTL work is interop evidence explicitly flagged "NOT legal status" in its own documentation. Violates #5 if phrased as status. |

---

## 4. Notes for page builders

- Quote phrasings verbatim where possible; weakening (dropping a number, adding a caveat) is allowed, strengthening is not.
- Every numeric claim on a page should carry a footnote-style reference to its claim id (e.g. `[OIDF-1]`) so reviewers can diff against this ledger.
- PQC mentions REQUIRE the PQ-1 limitation sentence on the same page, visually attached.
- Pricing always inside a sentence containing the word "indicative".
- The /evidence page may reproduce the source-artifact paths from §1 as provenance.
