Skip to content

feat: in-house IC-native personhood verification (#9072) - #9081

Draft
julianjelfs wants to merge 53 commits into
masterfrom
personhood_verification
Draft

feat: in-house IC-native personhood verification (#9072)#9081
julianjelfs wants to merge 53 commits into
masterfrom
personhood_verification

Conversation

@julianjelfs

@julianjelfs julianjelfs commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Revives unique personhood verification (#9072) as an in-house, on-chain, SNS-controlled system, replacing the sunsetted third-party DecideAI/AWS integration.

What this is (and isn't)

A new personhood_verifier canister runs the whole face-uniqueness pipeline on-chain under DAO governance. It is a probabilistic sybil-resistance layer at parity with what DecideAI provided — the same face-embedding primitive — but fully on the IC and SNS-controlled, with no third-party dependency and no biometric data leaving the chain.

It is not a cryptographic 1:N uniqueness guarantee at million scale. Face embeddings carry ~40–60 bits of effective separating entropy, so P(collision) = 1-(1-FMR)^N compounds — no single threshold is both strongly sybil-resistant and low-false-reject past low tens of thousands of users. This is an information-theoretic ceiling, not an implementation gap, and it applies equally to the DecideAI system this replaces. Positioned and tuned accordingly.

How it works

  • JPEG decode → UltraFace RFB-320 detection → insightface 2d106det landmarks → 5-point ArcFace alignment → w600k r50 ArcFace embedding → i8-quantized cosine uniqueness scan.
  • Liveness: canister-issued random head-pose challenge (Center/Left/Right/Up/Down), verified on-chain from uploaded frames.
  • Privacy: raw frames are heap-only and never persisted (structurally excluded from upgrade serialization); only the i8-quantized embedding is stored, and it is deleted on account deletion — or at any time via the profile's "Remove verification" button (remove_unique_person_proof), which revokes the proof and erases the embedding; the user can re-verify whenever they like.
  • One heavy inference per timer execution to stay inside deterministic time slicing.

On-chain feasibility (measured)

tract-onnx on wasm32 with SIMD. r50 embed = 24.76B instructions; the full Center-frame step (decode + detect + landmarks + embed) = 27.19B, under the 40B DTS per-message ceiling (~32% headroom). Deterministic across runs. Confirmed end-to-end on a local deployment.

Thresholds

r50-calibrated on LFW and SNS-governable at runtime (set_uniqueness_thresholds, governance-guarded, invariant clear ≤ retry ≤ duplicate, surfaced in /metrics) — the right value tracks the enrolled population, which grows. Launch defaults lean to low false-reject: T_dup 0.55 / clear 0.45 / retry 0.50 (~1% innocent rejection at ~100k enrolled). Raise by proposal as N grows.

Governance / lifecycle

  • Models are chunk-uploaded (inert) and activated only by a hash-pinned commit_model SNS proposal.
  • Legacy DecideAI proofs are wiped; a re-verification window + removal fan-out (LUI → user) handle the transition.
  • UniquePerson group/community gates are enforced against the new proofs.

Scope

Phases 0–3 (feasibility gate, backend skeleton, real on-chain pipeline, DecideAI cutover + UI) are complete. Phase 4 (production rollout) is the ordered SNS-proposal sequence, now fully documented in PRODUCTION_ROLLOUT.md (supersedes the earlier rollout comment on #9072). This PR also carries everything the rollout needs:

  • upload_model_chunks_whitelist init arg — dev principals upload (inert) model chunks in production, mirroring openchat_installer's wasm-chunk whitelist; activation stays proposal-only via the hash-pinned commit_model (~180×1MB chunk calls for the r50 model made proposal-per-chunk a non-starter).
  • commit_model, set_uniqueness_thresholds, set_personhood_verifier_canister_id and wipe_legacy_unique_person_proofs are now #[proposal] endpoints, exposing the _validate queries SNS generic-function registration requires.
  • Proposal scripts under scripts/proposals/: one-shot registration of the SNS functions (commit_model 11000, set_uniqueness_thresholds 11001 — verified unused against the live registry) plus an execution script per function. Per review, the verifier canister id is hardcoded (wji62-oiaaa-aaaaf-bsc7a-cai, created ahead of time) and the DecideAI wipe rides a designated upgrade's post_upgrade, so no wiring/wipe functions are needed.
  • verification_model_uploader --skip-commit — the production upload mode; prints the exact hash-pinned commit commands for the proposals.
  • Two-phase UI rollout via a build-time flag (OC_UNIQUE_PERSON_REQUIREMENTS_ENABLED): phase A ships the ability to verify (flow, badges, gate evaluator) while creating unique-person gates and prize restrictions stays unavailable; phase B — once real-world verification data looks healthy — is a website rebuild with the flag set, no code change. Verification strings are translated into all 14 locales.

Tests

personhood_verification 7/7 green. gated_group and delete_user suites cover gate enforcement and embedding deletion.

Notes for reviewers

  • New runtime dependency: @mediapipe/tasks-vision — client-side face tracking for the capture UX only (self-hosted assets, degraded manual-capture fallback if it fails to load). Not in the trust path; the canister re-derives everything on-chain.
  • CSP widened: script-src now includes 'wasm-unsafe-eval' so the MediaPipe wasm can instantiate. This is a deliberately widened surface — flagged here explicitly.
  • Per-stage instruction_counter logging in process_verifications is test_mode-gated (inert in production) — kept as a measurement aid for the r50 cost work; say if you'd prefer it stripped.
  • backend/personhood_bench/models/test_face.jpg is a committed determinism fixture (a public-figure portrait); model weights and wasms are gitignored.

Review response (addressed in follow-up commits)

  • Critical — stub-in-prod: start_verification returns NotReady and process_one_step parks rather than ever running the stub outside test_mode.
  • Critical — oversized-frame DoS: decode capped at 2048px (zune bails at the SOF header) + upload_frame rejects oversized dims via a new SOF parser before they enter the queue.
  • Risks: commit_model builds engines before the version-bump/notify and rolls back on failure; durable retry for unacknowledged proof notifications (persisted, retried by prune_sessions); cosine_similarity asserts equal length; MockVerifierClient throws on a prod misconfiguration; debug overlay gated on import.meta.env.DEV; saturating_sub on byte accounting.
  • Deferred (tracked): per-canister test_mode in the integration harness → prod-mode/oversized-frame tests; the remaining behavioural frontend items (getUserMedia timeout, poll clock, NotReady UX); session-cap split; i18n nits.

Refs #9072

julianjelfs and others added 30 commits July 8, 2026 15:30
Phase 0 hard gate for #9072: measures wasm instruction counts for
each on-chain pipeline stage (JPEG decode, face detect, landmarks,
embedding, uniqueness scan) under tract-onnx before any real build.
Models are downloaded (hash-pinned), not committed; tract crates get
opt-level 3 since "z" penalizes compute-bound inference.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 0 of #9072: session/challenge/status types in openchat-shared,
worker plumbing, and a scripted MockVerifierClient (used while no
verifier canister is configured) so the capture UX can be built and
device-tested with zero backend dependency. OC_VERIFIER_CANISTER is
wired through config but nothing selects a real client yet.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Largest single inference 5.5B instructions with SIMD (7x headroom
under the 40B DTS ceiling); a full 8-frame verification is ~30B
including decode and a 1M-user scan. getrandom needs a registered
custom impl on wasm32 since tract pulls rand.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shared headless state machine (consent -> camera -> pose challenge ->
upload -> processing -> result) drives new VerificationModal components
in the desktop and mobile trees, replacing the dormant DecideAI
VerifyHumanity modals behind the existing verify_humanity events.

- @mediapipe/tasks-vision (UX only: framing/pose guidance/auto-capture);
  assets self-hosted under /assets/verification, populated at build time
  by copy-verification-assets.sh, lazy-loaded when the flow opens
- detector load failure degrades to manual capture; canister re-verifies
  everything so security never depends on the client
- session starts only after camera + detector succeed, protecting the
  rate-limited attempts
- pose thresholds tunable via debug overlay
  (localStorage openchat_verification_debug=true)

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without the check, a component unmounting mid-capture let uploads,
step advancement and polling continue after destroy().

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1 of #9072, end-to-end with a deterministic stub engine:

- new personhood_verifier canister: session machine with pose
  challenges, chunked frame upload (heap-only, frames structurally
  excluded from upgrade serialization), processing queue + timer job,
  embedding store with brute-force cosine scan, three-band threshold
  policy with one stricter retry round
- test-mode engine fabricates embeddings from a marker byte so
  duplicate/gray-zone/retry paths are testable without real ML
- UniquePersonProof gains OpenChat provider + model_version (additive)
- user_index c2c_notify_personhood_verified (guarded) records the proof
  and reuses the existing NotifyUniquePersonProof fan-out untouched
- personhood_verifier_canister_id threaded openchat_installer ->
  user_index with serde defaults for upgrade safety
- registration: workspace, dfx.json, deploy/upgrade scripts, typebox
  script, integration test env + PocketIC coverage (happy path,
  duplicate, gray-zone retry, rate limit)

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plain xorshift from nearby seeds produced correlated vectors, making
unrelated markers falsely similar; use splitmix64 per element (unit
test pins the similarity bands). The PocketIC env is shared across
tests so each test gets its own marker group.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Local deployments are now testable from the UI end-to-end: the agent
uses the real client when OC_VERIFIER_CANISTER is set (sourced from
canister_ids.json for local dev) and falls back to the mock otherwise.

- typebox additions are appended to the committed typebox.ts rather
  than fully regenerated: a full regen pulls in unrelated backend type
  drift (e.g. HistoryDeleted) the frontend hasn't absorbed yet
- test-mode stub treats real JPEGs (which all start 0xFF, previously
  colliding with the ChallengeFailed marker) as unique faces hashed
  from the first frame, so real camera captures verify locally
- domain response types aligned with the canister API
  (invalid_challenge_index/total_bytes_exceeded/invalid_image,
  user_not_found/internal_error, not_submitted)

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-enables the desktop profile Verification section (uniquePersonGate
binding) and the mobile SparkleBox verify prompt so the new capture
flow is reachable. Gate bindings and badges stay stripped until the
cutover phase.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MediaPipe's face landmarker compiles self-hosted wasm, which script-src
blocks without 'wasm-unsafe-eval', forcing the capture flow into manual
mode. 'wasm-unsafe-eval' permits only WebAssembly compilation (from
sources already allowed by the policy), not JS eval.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device testing showed every pose instruction only passed when the user
did the opposite: MediaPipe's transformation-matrix rotation signs are
inverted relative to what the capture machine's thresholds assume, on
both axes. Negate yaw/pitch at extraction so "turn left" means the
subject's left. The on-chain challenge verification (Phase 2) must use
the same convention.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Svelte's bind:this sets the ref to null when the video unmounts (camera
view -> processing view), which crashed attachVideo and froze the modal
at "Verifying". attachVideo now treats a null/undefined element as a
detach: stop the detection loop and clear the reference.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 2 of #9072. The pipeline runs when models are committed and the
deterministic stub otherwise (integration tests, fresh local envs):

- model weights chunk-uploaded (inert, governance-guarded) and
  activated by hash-pinned commit_model; committing an embedding model
  bumps the model version, so stub-era enrollments (version 0) stop
  matching real ones
- inference: JPEG decode -> SCRFD-500M detection + 5 keypoints ->
  pose-challenge verification from keypoint geometry (same sign
  convention as the frontend) -> 5-point similarity alignment to the
  ArcFace template -> w600k_mbf 512-dim embedding; intra-session
  same-face check, mean + L2 + i8 quantization
- SCRFD + w600k_mbf are the insightface buffalo_sc pairing, replacing
  the 3-model lineup from the design (the 2d106det pose model is
  redundant given SCRFD's keypoints)
- DTS budgeting: one frame per timer execution (~10B instructions with
  SIMD), finalize + uniqueness scan separately; frames dropped the
  moment their frame is processed
- fails closed if models are committed but engines cannot build -
  never silently degrades to the stub
- wasm builds with +simd128 (generate-wasm.sh special-cases the
  verifier); tract requires a registered getrandom backend on wasm32
- verification_model_uploader tool + upload-verification-models.sh for
  local/test envs; production commits go via SNS proposals

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Envs upgraded from the stub era already sit at model version 1, so the
default commit version is rejected; pass a higher one as the third arg.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Envs whose verifier predates the governance_principals init arg have an
empty set after upgrade, locking model uploads out entirely. Controllers
can already reinstall the canister, so trusting them here grants nothing
extra; in production the only controller is SNS root.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Frontend: the framing oval turns green while the live pose satisfies
the current step, and each capture fires a green flash plus a short
synthesised two-tone blip (no audio asset; AudioContext created after
the consent click).

Canister: raise the geometric pose gains - the nose tip projects only
~0.5-0.7 of the eye distance, so the previous gains understated real
angles and rejected genuine captures. Per-frame yaw/pitch telemetry is
logged in test mode for calibration against real attempts.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tract miscompiles SCRFD's graph: outputs are nondeterministic (garbage
scores/coordinates varying across identical runs) and the unoptimized
reference plan fails shape inference on Add_109. Detection now uses
UltraFace RFB-320 (proven with tract in DFINITY's face-recognition
example) with insightface 2d106det supplying landmarks:

- SSD prior decode for 320x240 (4420 anchors, variances 0.1/0.2)
- UltraFace's coarse prior level double-fires a giant box around the
  same face which union-IoU NMS keeps; a containment merge drops the
  larger duplicate while preserving genuine multi-face rejection
- 5 alignment points from the 106-landmark set (eye ring centres 38/88,
  nose 86, mouth corners 52/61) - indices derived empirically and
  pinned by a native end-to-end test over a real portrait fixture,
  which also asserts pipeline determinism (the SCRFD failure mode)
- 2d106det takes raw 0-255 input; crop is 1.5x the detection box

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Genuine Center frames read pitch +10 to +14 degrees at the previous
neutral ratio (webcams sit below eye level), leaving almost no margin
under the Center threshold. 0.60 centres the observed distribution;
Up poses measured ~+50 so classification margins are unaffected.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Absolute pitch on genuine frontal frames varied +4..+20 degrees across
sessions with camera elevation and posture, so absolute thresholds
rejected real users. The challenge always opens with a Center step:
its measured pose (accepted within a broad plausibility window) now
anchors the session baseline and every later step is classified by its
delta, cancelling the bias. Observed genuine deltas - Up +45, Left
-52, Right +71, closing Center -4 - clear the 12-degree bands with
wide margins. Telemetry now logs deltas alongside absolute angles.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 3 of #9072, release-order coupled with the frontend un-strip:

- proof-removal fan-out (user_index -> local user indexes -> user
  canisters): proofs can now be revoked; user canisters report
  is_unique_person false transitions and keep earned achievements/CHIT
- stateless idempotent sweep job wipes all legacy DecideAI-provider
  proofs and, after an embedding model upgrade, lapses OpenChat proofs
  of superseded model versions at the announced deadline
- verifier announces model upgrades to user_index (90-day window) and
  purges superseded embeddings when the window closes
- DecideAI mint paths removed: submit_proof_of_unique_personhood
  endpoint deleted, local_user_index no longer accepts unique-person
  credential JWTs, proof_of_unique_personhood library retired
- unique person gate checks enforced for real (previously stubbed to
  always pass); composite-gate suspension filtering removed
- account deletion now erases the user's embedding in the verifier
- uniqueness-scan similarity telemetry in test mode for calibration

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 3 of #9072, release-order coupled with the backend cutover:

- reverts the sunset display-strips in both component trees: verified
  badge, unique person gate in the gate builder, prize uniquePersonOnly,
  gate icons/summaries, Learn-to-Earn achievement, profile sections;
  doesUserMeetAccessGate checks isUniquePerson for real again
- UniqueHumanGateEvaluator rewritten in both trees to run the capture
  flow inline via a shared VerificationFlow component (extracted from
  the verification modal); the gate is satisfied by the on-chain
  verification itself so no credential is produced or submitted
- DecideAI path deleted: submitProofOfUniquePersonhood across
  client/worker/agent, the dormant VerifyHumanity components, the
  uniquePersonCredentialGate constant, and the DecideAI i18n copy

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- unique person gates genuinely block unverified users and admit them
  after on-chain verification
- account deletion erases the verifier embedding (the same face
  re-enrolls cleanly instead of failing as a duplicate) - this test
  caught c2c_delete_user_embedding being a candid endpoint while both
  callers target the _msgpack method, so deletion silently never
  landed; the endpoint is now msgpack
- unit coverage for the proof sweep: DecideAI wipe and model-version
  lapse semantics

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The real pipeline shipped with UltraFace RFB-320 + 2d106det, not SCRFD
(tract miscompiled SCRFD); update the not-yet-released entry to match.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two SNS-proposal-driven controls that make a safe staged cutover
possible (canisters release one proposal at a time, not atomically):

- set_personhood_verifier_canister_id: on upgrade the id defaults to
  anonymous (init args do not re-run), which would leave a live
  user_index rejecting the verifier's proof notifications and skipping
  embedding deletion. Governance sets it after the verifier is
  registered.
- wipe_legacy_unique_person_proofs: the DecideAI wipe is now a
  deliberate one-shot governance action instead of firing on every
  upgrade, so the rollout controls exactly when legacy badges
  disappear. The removal job only arms when there is real work (a
  requested wipe or a due model-version lapse) rather than rescanning
  on every upgrade.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The face ML pipeline lived inside the personhood_verifier canister
(cdylib, so nothing could depend on it). Extracted the pure pipeline -
JPEG decode, UltraFace detection, 2d106det landmarks, ArcFace
alignment, w600k_mbf embedding, quantized cosine - into a shared
face_pipeline library with an owned Engines struct (no thread-local, no
IC deps). The canister's engine/real.rs is now a thin wrapper holding
the engines as canister state and mapping errors; the challenge-pose
classification (baseline deltas) stays canister-side. Behaviour is
unchanged - the canister and lib tests (real-portrait end-to-end,
determinism, pose signs) pass identically.

threshold_calibration is a native tool that runs that exact pipeline
over a labelled face dataset (LFW-style pairs) and reports the
genuine/impostor similarity distributions + ROC, so the SNS picks the
on-chain T_dup/T_clear bands from real false-match/false-non-match
numbers. It reports the scale-adjusted collision probability at
N=100k/1M - a small per-comparison FMR compounds when every enrolment
is scanned against the whole store, which is the number that actually
gates enforcement.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One command to fetch LFW and convert its pairs.txt into the
threshold_calibration input format.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The umass.edu LFW host frequently fails to resolve; scikit-learn mirrors
the dataset on figshare with correct checksums and lands the JPEGs on
disk, which the converter then points the calibration tool at.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Homebrew Python is PEP 668 externally-managed, so a system-wide pip
install fails. The script now creates a self-contained venv under
.calibration and installs scikit-learn there; system Python is
untouched.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Calibration on LFW exposed that ~29% of clean faces were rejected -
all as "multiple faces", none as "no face". UltraFace fires several
confident, overlapping/nested boxes on a single face; the old
post-processing (IoU-NMS + a keep-the-tighter containment merge) left
them separate, so the multi-face guard rejected the image, and when it
did pass it aligned off the tightest sub-box (a too-small crop that
also depressed genuine similarity).

cluster_faces now collapses all overlapping/nested boxes into one
representative per face (highest score wins) and drops boxes lying
largely outside the frame (UltraFace's coarse localization artifacts).
The multi-face guard fires only on a spatially separate second face.
LFW detection recall rose 75% -> 91% (the residual are real bystanders
in press photos, correctly rejected).

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Embedding now runs one engine per worker thread (no shared state, no
new deps): a full LFW run drops from ~40 min to ~6 min. Added a
--rgb-embed/BGR experiment path and used it to settle the channel
order: BGR is consistently worse on LFW (genuine median 0.580 vs
0.607), so this w600k_mbf ONNX export takes RGB, as shipped.

Refs #9072

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
julianjelfs and others added 4 commits July 10, 2026 13:51
Two security findings from review of #9081.

Stub-in-prod: process_one_step chose the engine only on
models.all_committed(), so during the rollout window (verifier
installed, models not yet committed) the deterministic stub granted
a genuine UniquePersonProof to every capture - and since each real
JPEG hashes to a distinct "face", one person could mint unlimited
proofs. start_verification now returns NotReady when
!test_mode && !all_committed(), and process_one_step parks rather
than ever running the stub outside test_mode.

Oversized-frame DoS: decode_jpeg used the 16384x16384 default cap, so
a highly-compressible large image within the frame byte limit blew
the DTS budget and trapped the timer message; the trap rolled back
the TIMER_ID reset, permanently wedging the global queue. decode_jpeg
now caps dimensions at 2048 (zune bails at the SOF header, cheaply),
and upload_frame rejects oversized frames via a new jpeg_dimensions
SOF parser before they enter the queue. Degenerate <2px dims rejected
to avoid the sample_bilinear width-1 underflow.

Also: cosine_similarity asserts equal length (fail closed vs scoring
the overlap into the uniqueness decision); upload_frame byte
accounting uses saturating_sub.

Refs #9072
Addresses the non-critical findings from review of #9081.

commit_model: build the engines (and roll the commit back on failure)
before bumping the version, starting the lapse window and notifying
user_index, so a build failure can't leave persisted state announcing
an upgrade the verifier can't serve.

Proof notifications are now durable: a verified user is recorded in a
persisted pending set and retried by prune_sessions until user_index
acknowledges, so a transiently-unavailable user_index (e.g. an
upgrade) can't strand a user as enrolled-but-proofless. Cleared on
ack and on account deletion.

cosine_similarity asserts equal length; byte accounting uses
saturating_sub.

Frontend: MockVerifierClient throws on a production build with no
verifier configured (was a silent mock); the verification debug
overlay is gated on import.meta.env.DEV in both trees.

Refs #9072
Resolve conflicts from frontend consolidation (#9063):
- adopt @client/@shared import aliases across personhood files
- move @mediapipe/tasks-vision dep to consolidated frontend/package.json
- port OC_VERIFIER_CANISTER define + copy-verification-assets.sh into
  restructured rollup.config.mjs
- keep VerifyHumanity.svelte deletions (replaced by verification flow)
- take master's EditableImageWrapper cropper fix (same fix both sides)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Missed from merge commit - npm install ran after staging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

canbench 🏋 (dir: .)

./canbench_results.yml is up to date ✅

---------------------------------------------------

Benchmark: add_reactions
  total:
    instructions: 1.36 B (0.07%) (change within noise threshold)
    heap_increase: 5 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: push_simple_text_messages
  total:
    instructions: 209.85 M (0.93%) (change within noise threshold)
    heap_increase: 12 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

julianjelfs and others added 5 commits July 11, 2026 16:04
First PR CI run surfaced pre-existing issues:
- cargo fmt over personhood files (long lines wrapped)
- clippy: needless_range_loop + assertions_on_constants in face_pipeline,
  manual_range_contains + collapsible_if + stale too_many_arguments expect
  in personhood_verifier, collapsible_if in user_index, unused variable in
  local_user_index (DecideAI removal leftover), unused mut in
  threshold_calibration
- exclude personhood_spike from CI clippy/test jobs: it include_bytes!s
  onnx models which are gitignored (fetched via download script)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add upload_model_chunks_whitelist so dev principals can upload (inert)
  model chunks in production; activation remains proposal-only via the
  hash-pinned commit_model
- Convert commit_model, set_uniqueness_thresholds,
  set_personhood_verifier_canister_id and wipe_legacy_unique_person_proofs
  to #[proposal] endpoints, generating the _validate queries required to
  register them as SNS generic functions
- Add proposal scripts (function ids 1016/1017/11000/11001, verified free
  against the live SNS registry) and a --skip-commit uploader mode for the
  production upload flow
- Document the full ordered rollout in PRODUCTION_ROLLOUT.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- OC_UNIQUE_PERSON_REQUIREMENTS_ENABLED build-time flag: phase A ships the
  ability to verify while the unique-person gate option and the prize
  message restriction stay unavailable; phase B is a website rebuild with
  the flag set - no code change or revert
- Decouple the profile Verification section from the gate binding so users
  can verify in phase A while the gate is not yet offered
- Translate the new verification strings into all 14 locales and drop the
  stale DecideAI strings everywhere
- Rework the rollout runbook for the two-phase website deploy

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Right to erasure: a new user_index remove_unique_person_proof endpoint
removes the caller's unique person proof (fanned out to the local user
indexes and their user canister) and deletes their face embedding and
attempt history from the personhood verifier, reusing the account-deletion
machinery. The user can re-verify at any time and their face is then
treated as brand new - which also makes repeated end-to-end testing
possible without hitting duplicate-face rejections.

The profile Verification section gains a Remove verification button behind
an AreYouSure confirmation that explains exactly what is deleted, with
strings in all 15 locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removing (or gaining) verification only updated the in-memory stores, so a
page refresh rehydrated the old status from the IndexedDB current-user and
user-summary caches until the next server round-trip. Follow the diamond
membership pattern: patch both caches when verification is removed or
succeeds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR revives “unique personhood verification” as an in-house, on-chain (SNS-governed) system by introducing a new personhood_verifier canister, wiring its proof lifecycle into user_index/user propagation, and re-enabling unique-person UX (verification flow, badge, and gates) with a two-phase rollout flag.

Changes:

  • Add the personhood_verifier canister (model chunk upload + hash-pinned commits, on-chain verification sessions, uniqueness scan, proof notifications, and embedding erasure paths).
  • Re-enable unique-person UI/gating and add a new human verification flow (web + mobile), guarded for “requirements” via OC_UNIQUE_PERSON_REQUIREMENTS_ENABLED.
  • Remove legacy DecideAI proof submission plumbing and add governance proposal scripts for cutover/operations.

Reviewed changes

Copilot reviewed 243 out of 248 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/upload-verification-models.sh Local helper to download and upload/commit verifier models.
scripts/upgrade-canister.sh Wires personhood_verifier canister id into upgrade tooling.
scripts/proposals/wipe_legacy_unique_person_proofs.sh Proposal script to wipe DecideAI proofs during cutover.
scripts/proposals/set_uniqueness_thresholds.sh Proposal script to tune on-chain uniqueness thresholds.
scripts/proposals/set_personhood_verifier_canister_id.sh Proposal script to set verifier id on user_index.
scripts/proposals/register_personhood_verifier_functions.sh Registers required SNS generic functions for governance.
scripts/proposals/commit_personhood_model.sh Proposal script to commit hash-pinned model uploads.
scripts/generate-wasm.sh Enables SIMD for personhood_verifier builds.
scripts/generate-typebox-types.sh Includes personhood_verifier in type generation.
scripts/generate-all-canister-wasms.sh Includes personhood_verifier in wasm build-all script.
scripts/download-personhood-spike-models.sh Downloads feasibility-spike ONNX models (hash checked).
scripts/deploy.sh Threads personhood_verifier into deploy tooling.
scripts/deploy-local.sh Adds personhood_verifier to local deployment set.
scripts/copy-verification-assets.sh Copies self-hosted MediaPipe assets for verification UX.
frontend/vite-env.d.ts Adds env typing for rollout flag (and should type verifier canister env).
frontend/package.json Adds MediaPipe tasks-vision dependency for capture UX.
frontend/package-lock.json Locks MediaPipe dependency.
frontend/openchat-worker/src/worker.ts Adds worker actions for start/upload/submit/status + removal/caching.
frontend/openchat-shared/src/domain/worker.ts Adds worker request/response types for verification flow.
frontend/openchat-shared/src/domain/verification.ts Adds shared domain types mirroring verifier canister API.
frontend/openchat-shared/src/domain/user/user.ts Removes DecideAI proof submission response type.
frontend/openchat-shared/src/domain/index.ts Re-exports verification domain types.
frontend/openchat-shared/src/domain/config.ts Adds optional verifier canister config to agent config.
frontend/openchat-shared/src/domain/access.ts Re-enables unique_person gate preprocessing; removes “suspended gate” stripping.
frontend/openchat-client/src/workerAgent.ts Passes verifierCanister into agent config.
frontend/openchat-client/src/config.ts Adds optional verifierCanister to client config.
frontend/openchat-agent/src/utils/userCache.ts Adds cache update for isUniquePerson.
frontend/openchat-agent/src/utils/chatsDb.ts Adds cached current-user unique-person flag update.
frontend/openchat-agent/src/services/verifier/verifier.client.ts Adds canister client for personhood_verifier API.
frontend/openchat-agent/src/services/userIndex/userIndex.client.ts Adds removeUniquePersonProof + durable cache sync helper; removes DecideAI submit call.
frontend/openchat-agent/src/services/userIndex/mappers.ts Removes DecideAI proof mapper; trims unused imports.
frontend/openchat-agent/src/services/openchatAgent.ts Routes verification calls to verifier client; removes DecideAI submission; enforces prod config.
frontend/openchat-agent/src/config.ts Adds optional verifierCanister to agent config.
frontend/app/vite.config.ts Runs verification asset copy script at build start.
frontend/app/src/utils/preview.svelte.ts Removes unique-person “suspended gate” stripping in previews.
frontend/app/src/utils/humanVerification/sound.ts Adds synthesized capture tone helper.
frontend/app/src/utils/humanVerification/poseDetector.ts Adds MediaPipe-based pose estimation (UX only, with fallback).
frontend/app/src/utils/humanVerification/capture.ts Adds JPEG capture/size ladder to meet canister budgets.
frontend/app/src/utils/featureFlags.ts Adds build-time flag for phase-B “requirements enabled”.
frontend/app/src/utils/access.ts Re-enables unique-person gate binding under feature flag; restores flattening semantics.
frontend/app/src/components/icons/Verified.svelte Restores rendering of verified badge controlled by prop.
frontend/app/src/components/home/VisibilityControl.svelte Removes suspended-gate stripping logic.
frontend/app/src/components/home/verification/VerificationModal.svelte Adds verification modal wrapper (web).
frontend/app/src/components/home/verification/VerificationDebugOverlay.svelte Adds optional debug overlay (web).
frontend/app/src/components/home/profile/UserProfileCard.svelte Minor import/style adjustments.
frontend/app/src/components/home/profile/UserProfile.svelte Adds verification section + “remove verification” flow.
frontend/app/src/components/home/profile/LearnToEarn.svelte Re-adds proved_unique_personhood achievement display.
frontend/app/src/components/home/profile/Badges.svelte Displays verified badge when uniquePerson is true.
frontend/app/src/components/home/PrizeContentBuilder.svelte Adds unique-person-only restriction UI under phase-B flag.
frontend/app/src/components/home/PrizeContent.svelte Enforces unique-person eligibility and shows restriction badge/CTA.
frontend/app/src/components/home/HumanityConfirmation.svelte Removes old DecideAI confirmation component.
frontend/app/src/components/home/Home.svelte Switches “verify humanity” modal to new verification modal.
frontend/app/src/components/home/CurrentChatMessages.svelte Removes suspended-gate stripping from preview gating logic.
frontend/app/src/components/home/communities/PreviewWrapper.svelte Removes suspended-gate stripping from join preview logic.
frontend/app/src/components/home/access/LeafGateBuilder.svelte Adds info text for unique-person gate.
frontend/app/src/components/home/access/AccessGateSummary.svelte Stops stripping unique-person gates in summary/edit flow.
frontend/app/src/components/home/access/AccessGateIconsForChat.svelte Updates merged gate icon rendering logic.
frontend/app/src/components/home/access/AccessGateEvaluator.svelte Re-enables unique-person gate evaluation with verification flow.
frontend/app/src/components/home/access/AccessGateControl.svelte Removes stripping in “bypass warning” logic.
frontend/app/src/components/home/access/AccessGateBuilder.svelte Re-enables unique-person gate editing/validation paths.
frontend/app/src/components/App.svelte Injects verifier canister env var into config.
frontend/app/src/components_mobile/icons/Verified.svelte Restores verified badge (mobile).
frontend/app/src/components_mobile/home/verification/VerificationModal.svelte Adds verification modal wrapper (mobile).
frontend/app/src/components_mobile/home/verification/VerificationDebugOverlay.svelte Adds optional debug overlay (mobile).
frontend/app/src/components_mobile/home/user_profile/Verify.svelte Removes DecideAI confirmation gating and updates consent text.
frontend/app/src/components_mobile/home/user_profile/UserProfileSummaryCard.svelte Shows verified account pill when unique-person verified.
frontend/app/src/components_mobile/home/user_profile/UserProfileSummary.svelte Adds verify CTA when user is not verified.
frontend/app/src/components_mobile/home/profile/LearnToEarn.svelte Re-adds proved_unique_personhood achievement display (mobile).
frontend/app/src/components_mobile/home/profile/Badges.svelte Displays verified badge when uniquePerson is true (mobile).
frontend/app/src/components_mobile/home/PrizeContentBuilder.svelte Adds unique-person restriction chip under phase-B flag (mobile).
frontend/app/src/components_mobile/home/PrizeContent.svelte Enforces unique-person eligibility and displays restriction (mobile).
frontend/app/src/components_mobile/home/PreviewFooter.svelte Re-enables unique-person gates in flattened join flow gates.
frontend/app/src/components_mobile/home/HumanityConfirmation.svelte Removes old DecideAI confirmation component (mobile).
frontend/app/src/components_mobile/home/Home.svelte Switches “verify humanity” modal to new verification modal (mobile).
frontend/app/src/components_mobile/home/CurrentChatMessages.svelte Removes suspended-gate stripping from preview gating (mobile).
frontend/app/src/components_mobile/home/communities/explore/Explore.svelte Refactors selected community card binding (mobile).
frontend/app/src/components_mobile/home/access/AccessGateEvaluator.svelte Re-enables unique-person gate evaluation via verification flow (mobile).
frontend/app/src/components_mobile/home/access_gates/AccessGates.svelte Removes suspended-gate stripping from gate presence logic (mobile).
frontend/app/src/components_mobile/App.svelte Injects verifier canister env var into config (mobile).
frontend/app/rollup.extras.mjs Sets OC_VERIFIER_CANISTER env (optional) and logs it.
frontend/app/rollup.config.mjs Defines import.meta.env.OC_VERIFIER_CANISTER at build time.
frontend/app/public/assets/verification/.gitignore Ignores copied/downloaded verification assets in repo.
dfx.json Adds personhood_verifier canister entry.
Cargo.toml Adds new canister crates, tools, spike crate, and face_pipeline lib; adjusts release opts for tract/zune.
backend/tools/verification_model_uploader/Cargo.toml Adds model uploader tool crate.
backend/tools/threshold_calibration/README.md Documents offline calibration tool and semantics.
backend/tools/threshold_calibration/Cargo.toml Adds calibration tool crate.
backend/tools/canister_upgrader/src/main.rs Adds CLI flag and routing for upgrading personhood_verifier.
backend/tools/canister_upgrader/src/lib.rs Adds upgrade function for personhood_verifier.
backend/tools/canister_upgrader/Cargo.toml Adds personhood_verifier api dependency.
backend/tools/canister_installer/src/main.rs Adds installer CLI flag for personhood_verifier.
backend/tools/canister_installer/src/lib.rs Installs personhood_verifier with init args; threads canister id.
backend/tools/canister_installer/Cargo.toml Adds personhood_verifier api dependency.
backend/personhood_spike/src/scan.rs Adds benchmark for i8 uniqueness scan cost.
backend/personhood_spike/src/main.rs Adds feasibility spike harness and deterministic wasm rand.
backend/personhood_spike/src/jpeg.rs Adds JPEG decode benchmark.
backend/personhood_spike/README.md Documents feasibility results and instruction counts.
backend/personhood_spike/models/.gitignore Ignores downloaded ONNX models.
backend/personhood_spike/Cargo.toml Adds spike crate dependencies and pinned tract version.
backend/personhood_spike/canbench.yml Adds canbench build/run config for spike.
backend/personhood_spike/canbench_results.yml Stores measured bench results.
backend/libraries/types/src/proof_of_uniqueness.rs Adds OpenChat provider + model_version field to unique person proofs.
backend/libraries/proof_of_unique_personhood/src/lib.rs Removes DecideAI credential verification library.
backend/libraries/proof_of_unique_personhood/Cargo.toml Removes DecideAI credential verification crate manifest.
backend/libraries/gated_groups/src/lib.rs Re-enables unique-person gate enforcement (no longer always pass/filter).
backend/libraries/face_pipeline/Cargo.toml Adds shared inference pipeline crate manifest.
backend/libraries/canister_agent_utils/src/lib.rs Adds personhood_verifier to canister id plumbing.
backend/integration_tests/src/wasms.rs Adds personhood_verifier wasm to test harness.
backend/integration_tests/src/setup.rs Installs personhood_verifier canister in PocketIC setup.
backend/integration_tests/src/lib.rs Adds personhood verification test module and canister ids field.
backend/integration_tests/src/client/user_index.rs Adds client call for remove_unique_person_proof.
backend/integration_tests/src/client/personhood_verifier.rs Adds verifier client calls and happy-path helpers.
backend/integration_tests/src/client/mod.rs Exposes verifier client module.
backend/integration_tests/Cargo.toml Adds personhood_verifier deps and serde_bytes.
backend/canisters/user/impl/src/updates/c2c_local_user_index.rs Handles unique-person proof removal event locally.
backend/canisters/user/impl/src/queries/updates.rs Ensures unique-person status updates propagate after removal.
backend/canisters/user/impl/src/lib.rs Stores unique_person_proof_removed_at for update propagation.
backend/canisters/user/CHANGELOG.md Notes unique person proof removal handling.
backend/canisters/user/api/src/lib.rs Adds NotifyUniquePersonProofRemoved event variant.
backend/canisters/user_index/impl/src/updates/wipe_legacy_unique_person_proofs.rs Adds governance proposal endpoint to trigger legacy wipe.
backend/canisters/user_index/impl/src/updates/submit_proof_of_unique_personhood.rs Removes DecideAI proof submission endpoint implementation.
backend/canisters/user_index/impl/src/updates/set_personhood_verifier_canister_id.rs Adds governance proposal endpoint to set verifier id.
backend/canisters/user_index/impl/src/updates/remove_unique_person_proof.rs Adds user endpoint to revoke proof + delete embedding/attempt history.
backend/canisters/user_index/impl/src/updates/mod.rs Wires new personhood update modules; removes DecideAI submission module.
backend/canisters/user_index/impl/src/updates/c2c_notify_personhood_verified.rs Records OpenChat-provider unique person proofs from verifier canister.
backend/canisters/user_index/impl/src/updates/c2c_notify_model_upgraded.rs Records model upgrade + lapse deadline and restarts lapse job.
backend/canisters/user_index/impl/src/model/user_map.rs Adds method to remove unique person proof from user map.
backend/canisters/user_index/impl/src/lifecycle/inspect_message.rs Allows new endpoints in inspect_message (user/governance guarded).
backend/canisters/user_index/impl/src/lifecycle/init.rs Threads personhood_verifier_canister_id into init.
backend/canisters/user_index/impl/src/jobs/mod.rs Starts lapse-removal job.
backend/canisters/user_index/impl/src/guards.rs Adds guard for personhood_verifier caller.
backend/canisters/user_index/impl/Cargo.toml Adds personhood_verifier dep; removes DecideAI proof dep.
backend/canisters/user_index/CHANGELOG.md Documents new governance endpoints + DecideAI removal.
backend/canisters/user_index/c2c_client/src/lib.rs Adds c2c calls for model upgrade + verification notify.
backend/canisters/user_index/api/src/updates/wipe_legacy_unique_person_proofs.rs Adds candid/TS types for wipe proposal endpoint.
backend/canisters/user_index/api/src/updates/submit_proof_of_unique_personhood.rs Removes DecideAI submission API.
backend/canisters/user_index/api/src/updates/set_personhood_verifier_canister_id.rs Adds API types for setting verifier id (proposal).
backend/canisters/user_index/api/src/updates/remove_unique_person_proof.rs Adds API types for user-initiated proof removal.
backend/canisters/user_index/api/src/updates/mod.rs Wires new update APIs; removes DecideAI submission API.
backend/canisters/user_index/api/src/updates/c2c_notify_personhood_verified.rs Adds API types for verifier→user_index proof notification.
backend/canisters/user_index/api/src/updates/c2c_notify_model_upgraded.rs Adds API types for verifier→user_index model upgrade notify.
backend/canisters/user_index/api/src/main.rs Generates TS methods for new endpoints; removes DecideAI endpoint.
backend/canisters/user_index/api/src/lifecycle/init.rs Adds init arg for personhood_verifier_canister_id.
backend/canisters/personhood_verifier/impl/src/updates/upload_model_chunk.rs Adds chunk upload endpoint guarded by whitelist.
backend/canisters/personhood_verifier/impl/src/updates/upload_frame.rs Adds frame upload endpoint with byte + dimension checks.
backend/canisters/personhood_verifier/impl/src/updates/submit_verification.rs Adds submission endpoint that queues session.
backend/canisters/personhood_verifier/impl/src/updates/set_uniqueness_thresholds.rs Adds governance endpoint to tune live thresholds.
backend/canisters/personhood_verifier/impl/src/updates/mod.rs Wires verifier canister updates.
backend/canisters/personhood_verifier/impl/src/updates/c2c_delete_user_embedding.rs Adds user_index→verifier embedding deletion endpoint.
backend/canisters/personhood_verifier/impl/src/queries/verification_status.rs Adds status query with queued position.
backend/canisters/personhood_verifier/impl/src/queries/model_info.rs Adds query for model version + enrollment count.
backend/canisters/personhood_verifier/impl/src/queries/mod.rs Wires verifier queries.
backend/canisters/personhood_verifier/impl/src/queries/http_request.rs Adds http_request for logs/metrics endpoints.
backend/canisters/personhood_verifier/impl/src/model/sessions.rs Adds heap-only session model with raw-frame exclusion.
backend/canisters/personhood_verifier/impl/src/model/mod.rs Wires model submodules.
backend/canisters/personhood_verifier/impl/src/model/embeddings.rs Adds embedding store + scan logic and cosine helper.
backend/canisters/personhood_verifier/impl/src/model/attempts.rs Adds attempt history windowing + retry allowance.
backend/canisters/personhood_verifier/impl/src/memory.rs Adds stable memory manager for upgrades/model chunks.
backend/canisters/personhood_verifier/impl/src/lifecycle/pre_upgrade.rs Serializes stable state excluding raw frames.
backend/canisters/personhood_verifier/impl/src/lifecycle/post_upgrade.rs Restores state/logs and re-inits env/cycles client.
backend/canisters/personhood_verifier/impl/src/lifecycle/mod.rs Wires init/upgrade lifecycle and RNG reseed.
backend/canisters/personhood_verifier/impl/src/lifecycle/init.rs Initializes verifier canister state and cycles client.
backend/canisters/personhood_verifier/impl/src/jobs/purge_lapsed_embeddings.rs Purges superseded-model embeddings after lapse window.
backend/canisters/personhood_verifier/impl/src/jobs/prune_sessions.rs Prunes sessions and retries pending proof notifications.
backend/canisters/personhood_verifier/impl/src/jobs/mod.rs Starts verifier jobs.
backend/canisters/personhood_verifier/impl/src/guards.rs Adds guards for user_index/governance/whitelist callers.
backend/canisters/personhood_verifier/impl/src/engine/mod.rs Defines thresholds and stub embedding generator entrypoint.
backend/canisters/personhood_verifier/impl/Cargo.toml Adds verifier canister implementation crate dependencies.
backend/canisters/personhood_verifier/CHANGELOG.md Documents verifier canister functionality and rollout.
backend/canisters/personhood_verifier/c2c_client/src/lib.rs Adds generated c2c client for embedding deletion.
backend/canisters/personhood_verifier/c2c_client/Cargo.toml Adds c2c client crate manifest.
backend/canisters/personhood_verifier/api/src/updates/upload_model_chunk.rs Adds upload_model_chunk API types.
backend/canisters/personhood_verifier/api/src/updates/upload_frame.rs Adds upload_frame API types.
backend/canisters/personhood_verifier/api/src/updates/submit_verification.rs Adds submit_verification API types.
backend/canisters/personhood_verifier/api/src/updates/start_verification.rs Adds start_verification API types and NotReady semantics.
backend/canisters/personhood_verifier/api/src/updates/set_uniqueness_thresholds.rs Adds set_uniqueness_thresholds API types.
backend/canisters/personhood_verifier/api/src/updates/mod.rs Wires verifier update APIs.
backend/canisters/personhood_verifier/api/src/updates/commit_model.rs Adds commit_model API types (hash pinned).
backend/canisters/personhood_verifier/api/src/updates/c2c_delete_user_embedding.rs Adds c2c_delete_user_embedding API types.
backend/canisters/personhood_verifier/api/src/queries/verification_status.rs Adds verification_status API types.
backend/canisters/personhood_verifier/api/src/queries/model_info.rs Adds model_info API types.
backend/canisters/personhood_verifier/api/src/queries/mod.rs Wires verifier query APIs.
backend/canisters/personhood_verifier/api/src/main.rs Generates TS bindings for verifier canister API.
backend/canisters/personhood_verifier/api/src/lifecycle/post_upgrade.rs Adds post-upgrade args API types.
backend/canisters/personhood_verifier/api/src/lifecycle/mod.rs Wires lifecycle API modules.
backend/canisters/personhood_verifier/api/src/lifecycle/init.rs Adds init args API types (whitelist + governance).
backend/canisters/personhood_verifier/api/src/lib.rs Defines public verifier API types and enums.
backend/canisters/personhood_verifier/api/Cargo.toml Adds verifier canister API crate manifest.
backend/canisters/openchat_installer/impl/src/updates/install_canisters.rs Threads verifier canister id into user_index init args.
backend/canisters/openchat_installer/impl/src/lifecycle/init.rs Threads verifier canister id into installer init.
backend/canisters/openchat_installer/impl/src/lib.rs Adds verifier canister id storage/defaulting and exposes it.
backend/canisters/openchat_installer/CHANGELOG.md Notes installer threading of verifier canister id.
backend/canisters/openchat_installer/api/src/lifecycle/init.rs Adds verifier canister id to installer init args API.
backend/canisters/local_user_index/impl/src/updates/c2c_notify_user_index_events.rs Propagates proof removal event and updates global map.
backend/canisters/local_user_index/impl/src/model/global_user_map.rs Adds unique-person proof removal from global map.
backend/canisters/local_user_index/impl/src/lib.rs Stops accepting DecideAI unique-person JWTs.
backend/canisters/local_user_index/impl/Cargo.toml Removes DecideAI proof lib dependency.
backend/canisters/local_user_index/CHANGELOG.md Notes DecideAI JWT removal and proof removal handling.
backend/canisters/local_user_index/api/src/lib.rs Adds user index event variant for proof removal.
backend/canisters/group/CHANGELOG.md Notes unique-person gate enforcement restored.
backend/canisters/community/CHANGELOG.md Notes unique-person gate enforcement restored.
.gitignore Adds .calibration/ ignore for calibration artifacts.
.github/workflows/backend.yaml Excludes personhood_spike from clippy/tests in CI.
Files not reviewed (1)
  • frontend/package-lock.json: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/app/src/components/home/access/AccessGateIconsForChat.svelte Outdated
translations_canister_id: CanisterId,
website_canister_id: CanisterId,
#[serde(default = "anonymous_principal")]
personhood_verifier_canister_id: CanisterId,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should create a new canister and then hardcode the canister id here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the canister now exists (wji62-oiaaa-aaaaf-bsc7a-cai) and is hardcoded as PERSONHOOD_VERIFIER_CANISTER_ID in the constants crate (baf77fe). The init-arg plumbing through the installer and user_index is gone; local deployments and the integration tests create the canister with that exact id (specified-id creation), so the same constant is correct in every environment.

// dev team in production, mirroring openchat_installer's
// upload_wasm_chunks_whitelist. Chunks only activate via a hash-pinned
// commit_model proposal, so this grants no control over the live models.
#[serde(default)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't give it a serde default - let's set the correct principals when we install

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — default removed in 09f5f35; installs must state the whitelist explicitly.

// on upgrade.
#[ts_export(user_index, wipe_legacy_unique_person_proofs)]
#[derive(CandidType, Serialize, Deserialize, Clone, Debug, HumanReadable)]
pub struct Args {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sems a bit OTT - I would have thought doing on user_index upgrade would be fine

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — endpoint removed in 09f5f35. The wipe is now a post_upgrade one-liner to ship in a designated user_index release once the verification UI has baked in production (it mustn't ride the first upgrade carrying this code, because users need a working way to re-verify before their badge disappears). The rollout runbook documents this, and voters see it in that release's changelog.

pub translations_canister_id: CanisterId,
pub registry_canister_id: CanisterId,
#[serde(default = "anonymous_principal")]
pub personhood_verifier_canister_id: CanisterId,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per a previous comment - let's create a canister ahead of time and hardcode the canister id here. And no need to have a new endpoint to set it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in baf77fe — the endpoint is removed along with its SNS function registration and script; the user_index authenticates the verifier against the hardcoded constant, so there is nothing to set.

@megrogan

Copy link
Copy Markdown
Collaborator

Should the personhood_spike be committed? Hasn't that done its job?

@julianjelfs

Copy link
Copy Markdown
Collaborator Author

Should the personhood_spike be committed? Hasn't that done its job?

I asked the same - apparently it's still doing a job, but I will ask it to rename to something less suspicious.

The crate outlived its Phase 0 feasibility-spike origin: it is the canbench
instruction-count harness to re-run before any model or tract upgrade, and
its models/ directory is the canonical location used by the model uploader,
threshold calibration and face_pipeline's end-to-end tests. The old name
confused people into thinking it was dead code. The download script loses
its -spike- too, and the README keeps the Phase 0 verdict as the historical
record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
// i8-quantized L2-normalized embeddings keyed by (model version, user).
// Heap-backed for the skeleton phase; the design moves this to a
// StableBTreeMap before real scale (100k users ~ 51MB serialized).
#[derive(Serialize, Deserialize, Default)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not start with a StableBTreeMap?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It now starts with one (09f5f35): a StableBTreeMap is the source of truth — nothing (de)serializes through upgrades however large the enrolled population gets — with a heap cache mirroring it for the scan path, rebuilt in post_upgrade. The cache exists because every verification brute-force scans all enrolled embeddings, and a contiguous heap scan benched ~247M instructions per 100k users (see personhood_bench), where deserializing every entry out of stable memory per probe would cost roughly an order of magnitude more.

julianjelfs and others added 6 commits July 13, 2026 16:09
- Embeddings: StableBTreeMap becomes the source of truth (keyed by
  version-prefixed user principal, nothing serialized through upgrades)
  with a heap scan cache rebuilt in post_upgrade, keeping the hot 1:N
  uniqueness scan on contiguous memory. Pre-hybrid heap embeddings are
  drained into the stable map on first upgrade.
- Replace the wipe_legacy_unique_person_proofs governance endpoint with a
  post_upgrade one-liner in a designated post-phase-A release; voters
  review it via the upgrade proposal changelog. SNS function 1017 and its
  scripts are gone.
- Drop the serde default from upload_model_chunks_whitelist - installs
  must state the whitelist explicitly.
- Guard AccessGateIconsForChat on merged.length so an empty merge result
  doesn't render an empty icons container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The production canister now exists (wji62-oiaaa-aaaaf-bsc7a-cai), so per
review the id becomes a constant and all the wiring machinery disappears:
the set_personhood_verifier_canister_id endpoint, its SNS function
registration and script, and the init-arg plumbing through
openchat_installer and user_index. The user_index authenticates the
verifier against the constant.

Local deployments (local_canister_creator + dfx.json specified_id) and the
integration tests (create_canister_with_id) create the canister with that
exact id, so the constant is correct in every environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds Section H to the OpenChat Terms (desktop + mobile pages) covering the
unique personhood verification service: what the camera challenge does,
exactly what biometric data is processed (in-memory frames, retained face
embedding, attempt history), Internet Computer node-provider visibility
during processing, purpose limitation, retention and the three deletion
triggers, consent withdrawal via Remove verification, and the automated
nature of the uniqueness decision.

The verification consent screen now links to Section H
(/terms?section=12) and the consent checkbox text references it
explicitly, in all 15 locales, so consent is demonstrably informed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Daily user_index job purges the unique person proof, face embedding and
  attempt history of users inactive for ~3 years (35 months, keeping the
  daily sweep inside the statutory 3-year destruction deadline). Cheap
  heap-timestamp candidate filter, confirmed against the online_users
  canister's real last-online data so active lurkers are never purged.
- The consent step now discloses purpose and full retention duration
  (consentPoint2, all 15 locales).
- Terms Section H6 gains the inactivity trigger and explicitly constitutes
  the written retention schedule and destruction guidelines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ests

Creating the personhood_verifier at its mainnet canister id makes PocketIC
spin up a third application subnet covering that id's range. The test setup
was then expanding OpenChat onto all three app subnets instead of the two
created by the PocketIcBuilder, changing user/community placement in every
test env and making tick-count-sensitive tests flaky (e.g.
leave_community_succeeds, channel_marked_as_read_after_joining).

Also stop returning the env to the shared pool after the biometric purge
test advances its clock by 3 years, so later tests can't inherit it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@julianjelfs

Copy link
Copy Markdown
Collaborator Author

I think we will probably end up not doing this since it's a bit controversial and really cannot be made to work well enough to satisfy the stated requirement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants