DecideID Verification
DecideID (DecideAI Proof-of-Personhood) is the second identity-verification method on ohshii_governance, alongside World ID. The two methods are independent: each has its own DAO-controlled config, its own per-method verified set, and its own enabled flag. The voting gate fires whenever either method is Required, and a user verified via either method satisfies the gate.
This document is the operator + developer manual for the DecideID integration. The World ID parallel doc is WORLD_ID_VERIFICATION.md; read both.
1. Configuration states
Section titled “1. Configuration states”| State | issuer_canister_id | enabled | Effect on ONS voting | What the user sees on /verify |
|---|---|---|---|---|
| Not configured | 2vxsx-fae (anonymous) | either | No DecideID gate. (World ID may still gate.) Verification flow refuses to start. | Dimmed DecideID method card with the badge “Not configured”. CTA disabled. |
| Optional | a real principal | false | No DecideID gate. (World ID may still gate.) Verification is still possible — useful when the DAO wants to A/B test the flow before flipping it required. | Lit DecideID card, badge “Optional”, CTA reads “Verify with DecideID (optional)”. |
| Required | a real principal | true | vote() rejects unless is_any_method_verified(caller) returns true. | Lit DecideID card, badge “Required for voting”, CTA reads “Start DecideID verification”. |
Important persistence rule: switching a method from Required/Optional back to Not Configured does NOT clear existing verifications. Once a principal is in DECIDEID_VERIFIED_USERS, the flag stays until an admin/DAO clears it via admin_clear_decideid_verified. This is enforced by the load-bearing comment on is_any_method_verified in memory.rs.
Important gate semantics — what happens when BOTH methods are disabled: the vote-gate condition is (world_id_cfg.enabled || decideid_cfg.enabled) && !is_any_method_verified(caller). With both enabled flags false, the first half of the AND is false, so the gate does not fire and ONS voting becomes open to any authenticated wallet — no verification required, no per-method check. Anonymous principals are still rejected by the reject_anonymous guard on vote(). All other voting rules (rate limit, voting power, proposal status) still apply. Concretely: a DAO that turns off both gates is choosing to remove the sybil filter altogether; a DAO that wants to keep voting paused without lifting the filter must leave at least one method enabled = true (typically with set_decideid_daily_cap(0) and/or set_world_id_daily_cap(0) to also block new verifications).
Combined state truth table (World ID enabled / DecideID enabled / outcome for an unverified caller):
WorldID .enabled | DecideID .enabled | ONS vote() for unverified caller |
|---|---|---|
| false | false | Allowed — gate is off, anyone authenticated can vote |
| true | false | Rejected unless caller is in VERIFIED_USERS OR DECIDEID_VERIFIED_USERS |
| false | true | Rejected unless caller is in VERIFIED_USERS OR DECIDEID_VERIFIED_USERS |
| true | true | Rejected unless caller is in VERIFIED_USERS OR DECIDEID_VERIFIED_USERS |
In every gated row, the OR over the verified-user sets is what answers “is this caller verified?” — the per-method enabled flags are ignored for that answer (persistence-on-disable). A caller verified via World ID passes a DecideID-only gate, and vice-versa.
State transitions:
- Not Configured → Optional / Required: ONS proposal
set_decideid_config(<issuer principal>, "<origin>", "<credential_type>", "<derivation_origin>", true|false, 300). - Optional ↔ Required: same proposal, flipping
enabled. - Emergency pause: ONS proposal
set_decideid_daily_cap(0)halts new verifications without touchingenabledor any existing verifications. - Rollback to Not Configured: ONS proposal
set_decideid_config(2vxsx-fae, "", "", "", false, 300)returns the integration to dormant. Existing verified users keep voting access.
Authoritative configuration values
Section titled “Authoritative configuration values”The values below are the current production-aligned configuration. They are the same on staging and production; only derivation_origin differs (each environment pins to its own canister’s native URL).
| Field | Value | Notes |
|---|---|---|
issuer_canister_id | qgxyr-pyaaa-aaaah-qdcwq-cai | DecideAI’s ProofOfUniqueness issuer; same canister hosts id.decideai.xyz. |
issuer_origin | https://id.decideai.xyz/ | WITH trailing slash — backend compares verbatim against the JWT iss claim. |
credential_type | ProofOfUniqueness | W3C credential type checked verbatim by the validator. |
derivation_origin (staging) | https://v5mnw-oaaaa-aaaal-qsica-cai.icp0.io | Native canister URL — DNS-renewal-independent. |
derivation_origin (production) | https://v5mnw-oaaaa-aaaal-qsica-cai.icp0.io | Same strategy, production canister. |
mode | variant { Vc } | Selects synchronous canister-side validation. |
enabled | true | Engages the OR-gate in vote(). |
vc_minimum_verification_date | "" | Empty → arguments: None. Set via set_decideid_vc_arguments only if the DAO wants to enforce a floor. |
vc_minimum_reputation_level | "" | Same as above. Accepted values when set: "gold" or "silver". |
Asymmetric trailing-slash semantics
Section titled “Asymmetric trailing-slash semantics”The cfg field issuer_origin is stored with a trailing slash because two consumers need the string in different forms:
| Consumer | Required form | Failure if wrong |
|---|---|---|
Backend validate_ii_presentation_and_claims | "https://id.decideai.xyz/" (slash) | Validator returns InconsistentCredentialJwtClaims("inconsistent claim in VC"). |
Frontend requestVerifiablePresentation SDK call | "https://id.decideai.xyz" (no slash) | Issuer canister’s derivation_origin API rejects with UnsupportedOrigin. |
The frontend modal applies .replace(/\/+$/, '') before passing the value to the SDK. Backend reads cfg verbatim. One source of truth, two readers, asymmetric handling isolated to the frontend.
derivation_origin strategy
Section titled “derivation_origin strategy”We pin derivation_origin to the native canister URL rather than a custom domain. This makes verification independent of custom-domain DNS / TLS renewals. DecideAI’s id.decideai.xyz advertises a matching ii-alternative-origins document that lists the canister URL, so the II alias-derivation path is consistent.
Implication: VC verification only succeeds when the user’s browser runtime origin matches the configured derivation_origin. Users on custom domains (e.g. launcher.ohshii.io) are bounced to the canonical native canister origin by the modal’s cross-origin guard before the flow starts.
Optional arguments (reputation gate, verification-date floor)
Section titled “Optional arguments (reputation gate, verification-date floor)”The ProofOfUniqueness credential supports two optional arguments:
| Argument | Type | Semantics |
|---|---|---|
minimumVerificationDate | ISO 8601 string | Reject presentations whose underlying user verification predates this date. |
minimumReputationLevel | "gold" | "silver" | Two-tier reputation gate. A user with a higher tier still passes a lower-tier check. |
Both are optional. Empty cfg fields produce arguments: None. When set via set_decideid_vc_arguments, the same values are forwarded by the frontend into the SDK request and verified verbatim by the backend — per DecideAI’s security note that requested and verified values must match.
2. End-user flow
Section titled “2. End-user flow”- User connects to OhShii with Internet Identity, NFID, or Plug.
- User opens
/verifyand clicks Start DecideID verification. DecideIDVerificationModalreadsget_decideid_config()from canister and callsrequestVerifiablePresentationfrom@dfinity/verifiable-credentials.- The library opens an Internet Identity popup. II authenticates the user, then opens the DecideID flow as the issuer (II → DecideID is a credential-fetching round-trip; OhShii is not in this loop).
- DecideID issues a JWT credential signed with its canister signature. II wraps it together with an
id_aliascredential (signed by II) into a Verifiable Presentation (JWT-VP). - The popup closes; the JWT-VP is delivered to OhShii’s frontend via
onSuccess. - The frontend forwards the raw JWT-VP to
register_decideid_verification(jwt_vp)on canister. - Canister validates synchronously (no inter-canister calls), records dedup digest, marks caller in
DECIDEID_VERIFIED_USERS, and writes a verification event to EVENT_LOG with emptydetails. - Frontend re-fetches
am_i_verified(); the universal banner flips to “Verified”.
3. Privacy model
Section titled “3. Privacy model”On canister, we store:
- Per-principal
verified = trueflag inDECIDEID_VERIFIED_USERS(no timestamp, no method-source metadata). - 32-byte SHA-256 of the JWT-VP in
USED_DECIDEID_PRESENTATIONS(dedup; cannot be reversed to recover the JWT). DecideIdConfig(issuer canister id, origin, credential type, our derivation origin, enabled, max age) — public, returned byget_decideid_config().- A verification event in EVENT_LOG with empty
details— caller is NOT logged.
On canister, we do NOT store:
- The JWT-VP itself.
- The id_alias credential payload.
- Any DecideAI-side principal, timestamp, or biometric metadata.
- A timestamp of the verification (would let observers time-correlate).
Unlinkability across relying parties is preserved by Internet Identity’s id_alias mechanism: DecideAI sees a per-(user, RP) alias that is different on every RP. DecideAI cannot correlate “user X on OhShii” with “user X on OpenChat”. OhShii cannot correlate either.
Per-method exposure on canister: the per-method introspection queries (is_world_id_verified, is_decideid_verified) are caller-only — they reject any call where the queried principal does not match msg_caller(). External enumeration of which provider a third-party principal used is not exposed. The universal gate query is_verified(p) returns only yes/no without provider information; that is the value used by the vote gate (see §9 Frontend integration).
4. Sybil resistance
Section titled “4. Sybil resistance”DecideID’s anti-sybil guarantee comes from two layers:
- DecideAI’s biometric uniqueness check (off-canister, off-OhShii). DecideAI verifies the user via biometric/document and refuses to issue a second credential to the same natural person. This is the upstream invariant we rely on; we do not enforce it ourselves.
- Alias-tuple binding (on-canister, in
validate_ii_presentation_and_claims). The id_alias credential commits to (caller’s RP-side principal, RP origin, alias principal). Validation rejects if the JWT’seffective_vc_subjectdoes not matchmsg_caller(), so user A’s JWT cannot be replayed by user B.
Defense-in-depth: we additionally hash the JWT-VP (SHA-256 → USED_DECIDEID_PRESENTATIONS) and reject any duplicate digest. This catches a class of bugs where a future change might weaken the alias-tuple binding.
What this does NOT prevent: a single user controlling multiple Internet Identity anchors (and getting DecideAI to verify each one separately) could in theory verify multiple OhShii principals. DecideAI’s published one-biometric-one-credential rule is the upstream defense; if DecideAI fails it, OhShii inherits the gap.
Downstream consumers of is_verified
Section titled “Downstream consumers of is_verified”is_verified is the OR over World ID and DecideID (is_any_method_verified), so completing DecideID is fully sufficient — a DecideID-only user is treated identically to a World-ID-only user by every downstream gate. In particular, the Sponsor/referral system is verification-gated on this same OR: a sponsor↔user referral connection is only persisted on-canister, and a sponsor only earns, once the invited user is verified by either method. Until then the referrer code stays pending client-side (no canister storage) and the referral reward stays with the OhShii treasury. The DecideID side carries the same attribution trigger as World ID — a successful register_decideid_verification confirms a pending referral (the same piggyback_attribute_sponsor path register_world_id_verification uses). The check is made at purchase time and is not retroactive to that buyer’s earlier unverified purchases. See SPONSOR_SYSTEM.md → Referral registration & verification gating and the symmetric note in WORLD_ID_VERIFICATION.md §4.
5. JWT verification
Section titled “5. JWT verification”Verification of the credential on the OhShii side is fully synchronous on-canister. No HTTPS outcalls. No threshold signing. No external dependencies from ohshii_governance. The pipeline lives in decideid_verification.rs and wraps the ic_verifiable_credentials crate (vendored under src/external_crates/, see VENDORED.md).
Important scope clarification: “no off-chain server” applies only to the canister-side verification of the JWT-VP. The underlying biometric check that DecideAI performs to issue the credential in the first place runs on DecideAI’s own off-chain infrastructure (face capture, liveness detection, document validation). Per the DecideAI Whitepaper, only a Zero-Knowledge proof of the result is then attested on-canister:
“The use of Zero-Knowledge (ZK) proofs means that in this process, the user’s credentials can be verified safely without the risk of sharing private information (e.g. biometric data). […] Decide ID sends back ZK proof — an encrypted version of the biometric and the verifier hash.”
So OhShii never receives biometric data; we only verify a ZK-attested Verifiable Credential. But the privacy claim “fully on-chain” applies to OUR verification step, not to DecideAI’s biometric pipeline.
What validate_ii_presentation_and_claims checks:
- JWT-VP is well-formed (id_alias credential + issuer credential, both as JWS).
- Both credentials’ canister signatures verify against the IC root key (fetched via
ic_cdk::api::root_key(), DER prefix stripped viaextract_raw_root_pk_from_der). effective_vc_subject == msg_caller()(alias-tuple binding).effective_derivation_origin == cfg.derivation_origin(RP-binding).- Issuer credential’s
credential_type == cfg.credential_type(issuer-binding). - JWT
expclaim is in the future relative tocurrent_time_ns.
What we add on top of the crate:
- 16 KB cap on
jwt_vp.len()to bound CPU and storage. - 32-byte SHA-256 dedup against
USED_DECIDEID_PRESENTATIONS.
Reserved (NOT currently enforced):
cfg.presentation_max_age_secondsis a placeholder for a future wallclock-style freshness bound stricter than the JWT’s intrinsicexpclaim. Today freshness relies solely on the validator’s internalcurrent_time_ns < expcheck — whatever TTL DecideAI puts on its credential is the actual ceiling. Setting a non-zero value has no runtime effect; the field is preserved in the Candid surface so adding the check later does not require a config migration. The missing check is non-exploitable in the current flow because: (a) same-caller idempotency short-circuits atis_decideid_verified(caller), and (b) cross-caller alias-tuple binding inside the validator rejects user A’s JWT submitted as user B.
Local replica caveat: DecideID’s issuer canister exists only on mainnet. Local-replica testing requires deploying a stub issuer canister against the local II (out of scope for v1). Recommendation: test in the Optional state on mainnet first, confirm the flow end-to-end, then flip to Required.
6. Rate limits and cycle budget
Section titled “6. Rate limits and cycle budget”| Limit | Default | Where enforced | Purpose |
|---|---|---|---|
| Per-call max JWT length | 16 KB | register_decideid_verification early-return | Bounds memory + hashing cost per call |
| Per-caller register attempts | 5 / hour, 30s cooldown | check_decideid_op_rate_limit (heap, non-persistent) | Primary DoS defense. Without this, an attacker generates N distinct invalid JWTs (any one-byte tweak → unique digest → bypasses dedup) from a single principal and burns the canister-wide daily cap. Bounds blast-radius at 5 attempts/hour per principal. |
| Daily cap on successful verifications | 1000 / 24h | consume_decideid_daily_cap_slot (stable memory) | Canister-wide ceiling on successful registrations. Consumed AFTER validate() succeeds, so invalid JWTs cannot drain it. |
| Per-caller in-flight register lock | 1 | DecideIdRegisterLockGuard (RAII, in-memory, #[must_use]) | Defensive: today the function is fully synchronous, so concurrent calls from the same principal cannot interleave anyway. Becomes load-bearing if a future change introduces an await in the flow. |
Order of defenses in register_decideid_verification (matters for DoS posture):
The critical ordering is 5 BEFORE 8 BEFORE 9: per-caller limit caps CPU spent by one attacker; cap consumed only on successful validation prevents canister-wide cap drain by invalid JWTs.
DecideID has no HTTPS outcalls and no threshold-ECDSA signing, so the per-verification cycle cost is dominated by the BLS canister-signature checks inside validate_ii_presentation_and_claims — single-digit billion cycles per call, not the ~100 B+ that a World ID verification consumes. The daily cap default is correspondingly higher (1000 vs. World ID’s 500).
7. Governance control
Section titled “7. Governance control”ONS proposal types (all ExecuteAdminMethod, all target ohshii_governance):
| Method | Args | Threshold | Purpose |
|---|---|---|---|
set_decideid_config | principal, text, text, text, bool, nat64 | Standard | Full config update (issuer canister, origin, credential_type, derivation_origin, enabled, presentation_max_age_seconds). Refuses to enable while issuer is anonymous. |
set_decideid_mode | variant { Vc; Oidc } | Standard | Selects the verification path. Vc = synchronous on-canister JWT-VP validation (default, free, no outcalls). Oidc = OAuth 2.0 path (parallel implementation, requires DCD; see §13). |
set_decideid_vc_arguments | text, text | Standard | Sets vc_minimum_verification_date (ISO 8601) and vc_minimum_reputation_level ("" / "gold" / "silver"). Empty strings disable the constraint and produce arguments: None. |
set_decideid_daily_cap | nat64 | Standard | Daily-cap update; 0 pauses. |
Important: the canister enforces that enabled cannot be true while issuer_canister_id == Principal::anonymous() — this prevents a misconfigured proposal from locking everyone out of voting (the gate would fire but no one could verify).
8. Admin escape hatches
Section titled “8. Admin escape hatches”All admin methods carry the is_admin_or_self_guard and are callable both by the admin allow-list and by ohshii_governance self-calls (DAO proposal execution).
| Method | Effect |
|---|---|
admin_clear_decideid_verified(principal) | Removes a principal from DECIDEID_VERIFIED_USERS. Returns true if the principal was previously verified. |
admin_clear_decideid_presentation(jwt_digest_hex) | Frees a recorded JWT-VP digest so the same JWT can be submitted again. Misuse weakens replay protection — use only for integration testing or documented incident response. |
There is no DecideID equivalent of admin_reset_world_id_rate_limit because DecideID has no per-caller rate-limit map; the per-caller DecideIdRegisterLockGuard lives only in memory and is automatically cleared on canister upgrade or after the in-flight register completes.
9. Frontend integration
Section titled “9. Frontend integration”The vote-gate decision MUST use the universal am_i_verified() query, never the per-method is_world_id_verified / is_decideid_verified. This is enforced by:
- Naming convention:
am_i_verified()for gating,am_i_verified_via(method)for badge display. - Code comments in
VotePanel.jsxexplicitly forbidding per-method gating. - Load-bearing comment on
is_any_method_verifiedinmemory.rsexplaining why the canister-side gate reads the verified-user sets directly and not theenabledflags.
CSP additions in .ic-assets.json5:
connect-srcaddshttps://id.decideai.xyz,https://qbw6f-caaaa-aaaah-qdcwa-cai.icp0.io, andhttps://qbw6f-caaaa-aaaah-qdcwa-cai.raw.icp0.io.frame-srcaddshttps://id.decideai.xyzandhttps://qbw6f-caaaa-aaaah-qdcwa-cai.icp0.io.https://identity.ic0.appwas already present (II popup hosts the actual VC flow UI).
npm dep: @dfinity/verifiable-credentials@^1.0.0 (lazy-loaded by VotePanel.jsx via React.lazy, so the chunk is fetched on demand only when an unverified user opens the modal).
10. Security properties
Section titled “10. Security properties”| Property | How it’s guaranteed |
|---|---|
| Anonymous callers rejected | #[ic_cdk::update(guard = "reject_anonymous")] on register_decideid_verification. |
| Caller binding (no cross-principal replay) | effective_vc_subject = msg_caller() passed to validate_ii_presentation_and_claims; the alias-tuple binding inside the validator rejects mismatches. |
| JWT replay (same caller, same JWT) | USED_DECIDEID_PRESENTATIONS dedup map; checked before the expensive crypto validation. |
| TOCTOU between dedup and commit | register_decideid_verification is synchronous (no .await boundaries). Dedup-check, validate, dedup-record, mark-verified are atomic from the IC scheduler’s perspective. |
| In-flight double-register from same principal | DecideIdRegisterLockGuard (per-caller BTreeSet<Principal>, released in Drop so a callback trap still frees the lock). Named binding required at the call site (let _register_lock = ...). |
| Storable-bound panic on oversized input | 16 KB cap on jwt_vp.len() checked before any storage write. |
| Daily CPU exhaustion under spam | DECIDEID_DAILY_CAP (default 1000) consumed atomically before validation. |
| Verification persists across method-disable | is_any_method_verified reads the verified-user sets directly, not the enabled flags. Documented with load-bearing comment. |
| Privacy: no caller in event log | The verification event is logged with empty details. |
11. Operator checklist
Section titled “11. Operator checklist”Before flipping to Required:
- Deploy the canister with
set_decideid_configproposal in Optional state. - Confirm
credential_typeempirically: have a tester run a real verification against a DecideID account (any authenticated principal can do this), observe the canister logs (orget_recent_events) for a successful verification event vs. validation errors. - If validation rejects with a
CredentialMismatch-shaped error, iterate via anotherset_decideid_configproposal with the corrected string. - Test the dual-CTA banner in
VotePanelwith an unverified principal (gate must offer both World ID and DecideID; CTAs must be disabled if the corresponding method is “Not Configured”). - Test the “Verified” banner on
/verifyafter a successful DecideID flow (am_i_verified()must return true;is_decideid_verified()must return true;is_world_id_verified()must return false unless the user also did World ID). - Once confident, submit
set_decideid_config(..., enabled = true, ...)to flip to Required.
Pause levers (in order of severity):
- Rate-limit pause: ONS proposal
set_decideid_daily_cap(0). Halts new verifications instantly. Existing verified users keep voting access. - Method disable: ONS proposal
set_decideid_config(<same principal>, ..., enabled = false, ...). Drops the gate to Optional; new verifications still possible but vote-gate doesn’t fire from this method. Existing verifications kept. - Full rollback: ONS proposal
set_decideid_config(2vxsx-fae, "", "", "", false, 300). Returns to Not Configured. Existing verifications kept.
What you cannot pause without a governance action: a verified user retains voting access until their flag is cleared via admin_clear_decideid_verified (executed by ohshii_governance via an approved proposal) or the canister is downgraded.
12. Implementation map
Section titled “12. Implementation map”| Concern | File |
|---|---|
| Vendored crate | src/external_crates/ic-verifiable-credentials/ |
DecideIdConfig Rust type | src/ohshii_governance/src/types.rs |
| Stable storage layout | src/ohshii_governance/src/memory.rs (MemoryIds 59–63 for VC core; 64–69 for OIDC parallel path) |
| VC verification logic | src/ohshii_governance/src/decideid_verification.rs |
| OIDC verification logic | src/ohshii_governance/src/decideid_oidc.rs |
| Public methods + vote gate | src/ohshii_governance/src/lib.rs |
| Candid interface | src/ohshii_governance/ohshii_governance.did |
| Frontend modal | src/frontend/src/components/Governance/DecideIDVerificationModal.jsx |
| OIDC callback route | src/frontend/src/components/Governance/DecideIDOidcCallback.jsx |
/verify page | src/frontend/src/pages/VerifyPage.jsx |
| Vote panel gate | src/frontend/src/components/Governance/VotePanel.jsx |
| Proposal form entries | src/frontend/src/components/Governance/CreateProposalForm.jsx |
| CSP allowances | src/frontend/.ic-assets.json5 |
| Bootstrap constants | src/frontend/src/config/decideid.js |
13. Production setup (VC mode, Required)
Section titled “13. Production setup (VC mode, Required)”The commands below configure DecideID in VC mode, enabled = true (Required-for-voting). In production these are executed by ohshii_governance via an approved ONS ExecuteAdminMethod proposal. The direct-dfx commands below are a bootstrap/recovery path only — the method names and argument order are identical when wrapped in a proposal.
Always re-run
set_decideid_configafter every deploy that adds fields toDecideIdConfig. The stable-storage migration may lossy-reset the cell, andget_decideid_configis the only authoritative confirmation that the values are live.
Prerequisites
Section titled “Prerequisites”# Verify you are on the secure_identity profile (bootstrap/recovery path; in production this config flows through an ONS proposal).dfx identity whoami# Expected: secure_identity
# Confirm the canister principal mapping for production.grep -n "ohshii_governance" canister_ids.json# Expected production: xnsdw-yaaaa-aaaal-qsqua-cai1. Apply core configuration
Section titled “1. Apply core configuration”Sets issuer, origin, credential type, derivation origin, and flips enabled = true in a single call.
dfx canister --network ic call ohshii_governance set_decideid_config '( principal "qgxyr-pyaaa-aaaah-qdcwq-cai", "https://id.decideai.xyz/", "ProofOfUniqueness", "https://v5mnw-oaaaa-aaaal-qsica-cai.icp0.io", true, 0 : nat64)'Field-by-field:
| Position | Argument | Value | Why |
|---|---|---|---|
| 1 | issuer_canister_id | qgxyr-pyaaa-aaaah-qdcwq-cai | DecideAI’s ProofOfUniqueness issuer. |
| 2 | issuer_origin | https://id.decideai.xyz/ | WITH trailing slash — required for backend JWT iss match. |
| 3 | credential_type | ProofOfUniqueness | Verbatim W3C credential type. |
| 4 | derivation_origin | https://v5mnw-oaaaa-aaaal-qsica-cai.icp0.io | Production canister native URL (DNS-renewal-independent). |
| 5 | enabled | true | Engages the OR-gate in vote(). Required for production. |
| 6 | presentation_max_age_seconds | 0 | Reserved field, no runtime effect today. |
2. Select VC mode
Section titled “2. Select VC mode”dfx canister --network ic call ohshii_governance set_decideid_mode '(variant { Vc })'3. (Optional) Set VC arguments
Section titled “3. (Optional) Set VC arguments”Skip this step unless the DAO wants to enforce a verification-date floor or a reputation tier. Empty strings restore arguments: None (the OpenChat-equivalent default).
# No extra constraint (default; same as never calling it):dfx canister --network ic call ohshii_governance set_decideid_vc_arguments '("", "")'
# Example: require gold-tier reputation:# dfx canister --network ic call ohshii_governance set_decideid_vc_arguments '("", "gold")'
# Example: require verification dated on or after 2026-01-01:# dfx canister --network ic call ohshii_governance set_decideid_vc_arguments '("2026-01-01T00:00:00Z", "")'4. Verify the configuration is live
Section titled “4. Verify the configuration is live”dfx canister --network ic call ohshii_governance get_decideid_configExpected fields:
record { issuer_canister_id = principal "qgxyr-pyaaa-aaaah-qdcwq-cai"; issuer_origin = "https://id.decideai.xyz/"; credential_type = "ProofOfUniqueness"; derivation_origin = "https://v5mnw-oaaaa-aaaal-qsica-cai.icp0.io"; enabled = true; mode = variant { Vc }; vc_minimum_verification_date = ""; vc_minimum_reputation_level = ""; ...}5. Smoke-test end-to-end
Section titled “5. Smoke-test end-to-end”- Open
https://v5mnw-oaaaa-aaaal-qsica-cai.icp0.io/verify(or whichever canonical URL you advertise) with a wallet that has completed DecideID Proof-of-Personhood atid.decideai.xyz. - Click Start DecideID verification.
- The II popup opens, completes the credential round-trip, and closes.
- The page flips to “Verified” and
am_i_verified()returnstrue.
If the popup completes but the modal shows a validation error, inspect the canister with:
dfx canister --network ic call ohshii_governance get_recent_events '(opt 20 : opt nat32)'Switching to OIDC mode (alternative path)
Section titled “Switching to OIDC mode (alternative path)”OIDC is an alternative DecideAI verification path that uses HTTPS outcalls + DCD billing. It exists for environments where the on-canister VC pipeline cannot be used. The toggle is a single proposal:
dfx canister --network ic call ohshii_governance set_decideid_mode '(variant { Oidc })'Switching modes does not invalidate existing verifications — DECIDEID_VERIFIED_USERS is persisted independently of which path produced the entry.
Rollback / emergency pause
Section titled “Rollback / emergency pause”| Lever | Command | Effect |
|---|---|---|
| Halt new verifications, keep gate active | dfx canister --network ic call ohshii_governance set_decideid_daily_cap '(0 : nat64)' | Existing verified users keep voting; new attempts rejected. |
| Disable the gate, keep verifications | dfx canister --network ic call ohshii_governance set_decideid_config '(principal "qgxyr-pyaaa-aaaah-qdcwq-cai", "https://id.decideai.xyz/", "ProofOfUniqueness", "https://v5mnw-oaaaa-aaaal-qsica-cai.icp0.io", false, 0 : nat64)' | Method becomes Optional. Vote-gate from DecideID stops firing. |
| Full rollback to Not Configured | dfx canister --network ic call ohshii_governance set_decideid_config '(principal "2vxsx-fae", "", "", "", false, 0 : nat64)' | Method dormant. Existing verifications kept (clear via admin_clear_decideid_verified if needed). |