[{"data":1,"prerenderedAt":8383},["ShallowReactive",2],{"navigation":3,"\u002Fservices\u002Fsession-tiers":927,"mdc-uw1ati-key":8306,"mdc-r6p8ur-key":8317,"mdc--qkgqlj-key":8344,"mdc-fywk04-key":8360},[4,40,114,446,474,498,596,903],{"title":5,"path":6,"stem":7,"children":8},"Architecture","\u002Farchitecture","architecture",[9,14,28,32,36],{"title":10,"path":6,"stem":11,"access":12,"icon":13},"Overview","architecture\u002Findex","public","i-lucide-network",{"title":10,"path":15,"stem":16,"children":17,"access":12,"icon":19},"\u002Farchitecture\u002Fkernel","architecture\u002F1.kernel\u002Findex",[18,20,24],{"title":10,"path":15,"stem":16,"access":12,"icon":19},"i-lucide-box",{"title":21,"path":22,"stem":23,"access":12},"Enclave","\u002Farchitecture\u002Fkernel\u002Fenclave","architecture\u002F1.kernel\u002Fenclave",{"title":25,"path":26,"stem":27,"access":12},"Forking the enclave","\u002Farchitecture\u002Fkernel\u002Fforking-the-enclave","architecture\u002F1.kernel\u002Fforking-the-enclave",{"title":29,"path":30,"stem":31,"access":12},"Ports","\u002Farchitecture\u002Fports","architecture\u002F2.ports",{"title":33,"path":34,"stem":35,"access":12},"Service layers","\u002Farchitecture\u002Fservice-layers","architecture\u002F3.service-layers",{"title":37,"path":38,"stem":39,"access":12},"Families","\u002Farchitecture\u002Ffamilies","architecture\u002F4.families",{"title":41,"path":42,"stem":43,"children":44},"Core","\u002Fcore","core",[45,47,50,53,56,88,97],{"title":10,"path":42,"stem":46,"access":12,"icon":19},"core\u002Findex",{"title":10,"path":48,"stem":49,"access":12},"\u002Fcore\u002F01-overview","core\u002F01-overview",{"title":10,"path":51,"stem":52,"access":12},"\u002Fcore\u002F05-stores","core\u002F05-stores",{"title":10,"path":54,"stem":55,"access":12},"\u002Fcore\u002F06-permissions-and-keyring","core\u002F06-permissions-and-keyring",{"title":10,"path":57,"stem":58,"children":59,"access":12},"\u002Fcore\u002Fnoydb","core\u002F2.noydb\u002Findex",[60,61,65,68,72,76,80,84],{"title":10,"path":57,"stem":58,"access":12},{"title":62,"path":63,"stem":64,"access":12},"Noydb","\u002Fcore\u002Fnoydb\u002Fmethods","core\u002F2.noydb\u002F1.methods",{"title":21,"path":66,"stem":67,"access":12},"\u002Fcore\u002Fnoydb\u002Fenclave","core\u002F2.noydb\u002F2.enclave",{"title":69,"path":70,"stem":71,"access":12},"Secret & keyring","\u002Fcore\u002Fnoydb\u002Fkeyring","core\u002F2.noydb\u002F3.keyring",{"title":73,"path":74,"stem":75,"access":12},"Authentication","\u002Fcore\u002Fnoydb\u002Fauthentication","core\u002F2.noydb\u002F4.authentication",{"title":77,"path":78,"stem":79,"access":12},"Auth recovery","\u002Fcore\u002Fnoydb\u002Frecovery","core\u002F2.noydb\u002F5.recovery",{"title":81,"path":82,"stem":83,"access":12},"Configuration","\u002Fcore\u002Fnoydb\u002Fconfiguration","core\u002F2.noydb\u002F6.configuration",{"title":85,"path":86,"stem":87,"access":12},"Strategies","\u002Fcore\u002Fnoydb\u002Fstrategies","core\u002F2.noydb\u002F7.strategies",{"title":10,"path":89,"stem":90,"children":91,"access":12},"\u002Fcore\u002Fvault","core\u002F3.vault\u002Findex",[92,93],{"title":10,"path":89,"stem":90,"access":12},{"title":94,"path":95,"stem":96,"access":12},"Vault","\u002Fcore\u002Fvault\u002Fmethods","core\u002F3.vault\u002F1.methods",{"title":10,"path":98,"stem":99,"children":100,"access":12},"\u002Fcore\u002Fcollection","core\u002F4.collection\u002Findex",[101,102,106,110],{"title":10,"path":98,"stem":99,"access":12},{"title":103,"path":104,"stem":105,"access":12},"Collection","\u002Fcore\u002Fcollection\u002Fmethods","core\u002F4.collection\u002F1.methods",{"title":107,"path":108,"stem":109,"access":12},"Querying","\u002Fcore\u002Fcollection\u002Fquerying","core\u002F4.collection\u002F2.querying",{"title":111,"path":112,"stem":113,"access":12},"Schema","\u002Fcore\u002Fcollection\u002Fschema","core\u002F4.collection\u002F3.schema",{"title":37,"path":115,"stem":116,"children":117},"\u002Ffamilies","families",[118,121,168,194,208,212,216,294,340],{"title":10,"path":115,"stem":119,"access":12,"icon":120},"families\u002Findex","i-lucide-plug",{"title":122,"path":123,"stem":124,"children":125,"access":12,"icon":127},"as-Exports","\u002Ffamilies\u002Fas","families\u002Fas\u002Findex",[126,128,132,136,140,144,148,152,156,160,164],{"title":122,"path":123,"stem":124,"access":12,"icon":127},"i-lucide-book-open",{"title":129,"path":130,"stem":131,"access":12},"aws-s3","\u002Ffamilies\u002Fas\u002Faws-s3","families\u002Fas\u002Faws-s3",{"title":133,"path":134,"stem":135,"access":12},"blob","\u002Ffamilies\u002Fas\u002Fblob","families\u002Fas\u002Fblob",{"title":137,"path":138,"stem":139,"access":12},"csv","\u002Ffamilies\u002Fas\u002Fcsv","families\u002Fas\u002Fcsv",{"title":141,"path":142,"stem":143,"access":12},"json","\u002Ffamilies\u002Fas\u002Fjson","families\u002Fas\u002Fjson",{"title":145,"path":146,"stem":147,"access":12},"ndjson","\u002Ffamilies\u002Fas\u002Fndjson","families\u002Fas\u002Fndjson",{"title":149,"path":150,"stem":151,"access":12},"noydb","\u002Ffamilies\u002Fas\u002Fnoydb","families\u002Fas\u002Fnoydb",{"title":153,"path":154,"stem":155,"access":12},"sql","\u002Ffamilies\u002Fas\u002Fsql","families\u002Fas\u002Fsql",{"title":157,"path":158,"stem":159,"access":12},"xlsx","\u002Ffamilies\u002Fas\u002Fxlsx","families\u002Fas\u002Fxlsx",{"title":161,"path":162,"stem":163,"access":12},"xml","\u002Ffamilies\u002Fas\u002Fxml","families\u002Fas\u002Fxml",{"title":165,"path":166,"stem":167,"access":12},"zip","\u002Ffamilies\u002Fas\u002Fzip","families\u002Fas\u002Fzip",{"title":169,"path":170,"stem":171,"children":172,"access":12,"icon":127},"at-Seals","\u002Ffamilies\u002Fat","families\u002Fat\u002Findex",[173,174,178,182,186,190],{"title":169,"path":170,"stem":171,"access":12,"icon":127},{"title":175,"path":176,"stem":177,"access":12},"aws-kms","\u002Ffamilies\u002Fat\u002Faws-kms","families\u002Fat\u002Faws-kms",{"title":179,"path":180,"stem":181,"access":12},"azure-keyvault","\u002Ffamilies\u002Fat\u002Fazure-keyvault","families\u002Fat\u002Fazure-keyvault",{"title":183,"path":184,"stem":185,"access":12},"env","\u002Ffamilies\u002Fat\u002Fenv","families\u002Fat\u002Fenv",{"title":187,"path":188,"stem":189,"access":12},"gcp-kms","\u002Ffamilies\u002Fat\u002Fgcp-kms","families\u002Fat\u002Fgcp-kms",{"title":191,"path":192,"stem":193,"access":12},"macos-keychain","\u002Ffamilies\u002Fat\u002Fmacos-keychain","families\u002Fat\u002Fmacos-keychain",{"title":195,"path":196,"stem":197,"children":198,"access":12,"icon":127},"by-Sessions","\u002Ffamilies\u002Fby","families\u002Fby\u002Findex",[199,200,204],{"title":195,"path":196,"stem":197,"access":12,"icon":127},{"title":201,"path":202,"stem":203,"access":12},"peer","\u002Ffamilies\u002Fby\u002Fpeer","families\u002Fby\u002Fpeer",{"title":205,"path":206,"stem":207,"access":12},"tabs","\u002Ffamilies\u002Fby\u002Ftabs","families\u002Fby\u002Ftabs",{"title":209,"path":210,"stem":211,"access":12},"Classifying a package","\u002Ffamilies\u002Fclassification","families\u002Fclassification",{"title":213,"path":214,"stem":215,"access":12},"Command-line tools","\u002Ffamilies\u002Fcli","families\u002Fcli",{"title":217,"path":218,"stem":219,"children":220,"access":12,"icon":127},"in-Frameworks","\u002Ffamilies\u002Fin","families\u002Fin\u002Findex",[221,222,226,230,234,238,242,246,250,254,258,262,266,270,274,278,282,286,290],{"title":217,"path":218,"stem":219,"access":12,"icon":127},{"title":223,"path":224,"stem":225,"access":12},"ai","\u002Ffamilies\u002Fin\u002Fai","families\u002Fin\u002Fai",{"title":227,"path":228,"stem":229,"access":12},"devtools","\u002Ffamilies\u002Fin\u002Fdevtools","families\u002Fin\u002Fdevtools",{"title":231,"path":232,"stem":233,"access":12},"devtools-tui","\u002Ffamilies\u002Fin\u002Fdevtools-tui","families\u002Fin\u002Fdevtools-tui",{"title":235,"path":236,"stem":237,"access":12},"liff","\u002Ffamilies\u002Fin\u002Fliff","families\u002Fin\u002Fliff",{"title":239,"path":240,"stem":241,"access":12},"nextjs","\u002Ffamilies\u002Fin\u002Fnextjs","families\u002Fin\u002Fnextjs",{"title":243,"path":244,"stem":245,"access":12},"nuxt","\u002Ffamilies\u002Fin\u002Fnuxt","families\u002Fin\u002Fnuxt",{"title":247,"path":248,"stem":249,"access":12},"pinia","\u002Ffamilies\u002Fin\u002Fpinia","families\u002Fin\u002Fpinia",{"title":251,"path":252,"stem":253,"access":12},"pwa","\u002Ffamilies\u002Fin\u002Fpwa","families\u002Fin\u002Fpwa",{"title":255,"path":256,"stem":257,"access":12},"react","\u002Ffamilies\u002Fin\u002Freact","families\u002Fin\u002Freact",{"title":259,"path":260,"stem":261,"access":12},"relay","\u002Ffamilies\u002Fin\u002Frelay","families\u002Fin\u002Frelay",{"title":263,"path":264,"stem":265,"access":12},"rest","\u002Ffamilies\u002Fin\u002Frest","families\u002Fin\u002Frest",{"title":267,"path":268,"stem":269,"access":12},"solid","\u002Ffamilies\u002Fin\u002Fsolid","families\u002Fin\u002Fsolid",{"title":271,"path":272,"stem":273,"access":12},"svelte","\u002Ffamilies\u002Fin\u002Fsvelte","families\u002Fin\u002Fsvelte",{"title":275,"path":276,"stem":277,"access":12},"tanstack-query","\u002Ffamilies\u002Fin\u002Ftanstack-query","families\u002Fin\u002Ftanstack-query",{"title":279,"path":280,"stem":281,"access":12},"tanstack-table","\u002Ffamilies\u002Fin\u002Ftanstack-table","families\u002Fin\u002Ftanstack-table",{"title":283,"path":284,"stem":285,"access":12},"vue","\u002Ffamilies\u002Fin\u002Fvue","families\u002Fin\u002Fvue",{"title":287,"path":288,"stem":289,"access":12},"yjs","\u002Ffamilies\u002Fin\u002Fyjs","families\u002Fin\u002Fyjs",{"title":291,"path":292,"stem":293,"access":12},"zustand","\u002Ffamilies\u002Fin\u002Fzustand","families\u002Fin\u002Fzustand",{"title":295,"path":296,"stem":297,"children":298,"access":12,"icon":127},"on-Auth","\u002Ffamilies\u002Fon","families\u002Fon\u002Findex",[299,300,304,308,312,316,320,324,328,332,336],{"title":295,"path":296,"stem":297,"access":12,"icon":127},{"title":301,"path":302,"stem":303,"access":12},"email-otp","\u002Ffamilies\u002Fon\u002Femail-otp","families\u002Fon\u002Femail-otp",{"title":305,"path":306,"stem":307,"access":12},"magic-link","\u002Ffamilies\u002Fon\u002Fmagic-link","families\u002Fon\u002Fmagic-link",{"title":309,"path":310,"stem":311,"access":12},"oidc","\u002Ffamilies\u002Fon\u002Foidc","families\u002Fon\u002Foidc",{"title":313,"path":314,"stem":315,"access":12},"password","\u002Ffamilies\u002Fon\u002Fpassword","families\u002Fon\u002Fpassword",{"title":317,"path":318,"stem":319,"access":12},"pin","\u002Ffamilies\u002Fon\u002Fpin","families\u002Fon\u002Fpin",{"title":321,"path":322,"stem":323,"access":12},"recovery","\u002Ffamilies\u002Fon\u002Frecovery","families\u002Fon\u002Frecovery",{"title":325,"path":326,"stem":327,"access":12},"shamir","\u002Ffamilies\u002Fon\u002Fshamir","families\u002Fon\u002Fshamir",{"title":329,"path":330,"stem":331,"access":12},"threat","\u002Ffamilies\u002Fon\u002Fthreat","families\u002Fon\u002Fthreat",{"title":333,"path":334,"stem":335,"access":12},"totp","\u002Ffamilies\u002Fon\u002Ftotp","families\u002Fon\u002Ftotp",{"title":337,"path":338,"stem":339,"access":12},"webauthn","\u002Ffamilies\u002Fon\u002Fwebauthn","families\u002Fon\u002Fwebauthn",{"title":341,"path":342,"stem":343,"children":344,"access":12,"icon":127},"to-Stores","\u002Ffamilies\u002Fto","families\u002Fto\u002Findex",[345,346,350,353,357,361,365,370,374,378,382,386,390,394,398,402,406,410,414,417,421,425,429,434,438,442],{"title":341,"path":342,"stem":343,"access":12,"icon":127},{"title":347,"path":348,"stem":349,"access":12},"aws-dynamo","\u002Ffamilies\u002Fto\u002Faws-dynamo","families\u002Fto\u002Faws-dynamo",{"title":129,"path":351,"stem":352,"access":12},"\u002Ffamilies\u002Fto\u002Faws-s3","families\u002Fto\u002Faws-s3",{"title":354,"path":355,"stem":356,"access":12},"browser-fs","\u002Ffamilies\u002Fto\u002Fbrowser-fs","families\u002Fto\u002Fbrowser-fs",{"title":358,"path":359,"stem":360,"access":12},"browser-idb","\u002Ffamilies\u002Fto\u002Fbrowser-idb","families\u002Fto\u002Fbrowser-idb",{"title":362,"path":363,"stem":364,"access":12},"browser-local","\u002Ffamilies\u002Fto\u002Fbrowser-local","families\u002Fto\u002Fbrowser-local",{"title":366,"path":367,"stem":368,"access":12,"icon":369},"Choosing a store","\u002Ffamilies\u002Fto\u002Fchoosing-a-store","families\u002Fto\u002Fchoosing-a-store","i-lucide-split",{"title":371,"path":372,"stem":373,"access":12},"cloudflare-d1","\u002Ffamilies\u002Fto\u002Fcloudflare-d1","families\u002Fto\u002Fcloudflare-d1",{"title":375,"path":376,"stem":377,"access":12},"cloudflare-r2","\u002Ffamilies\u002Fto\u002Fcloudflare-r2","families\u002Fto\u002Fcloudflare-r2",{"title":379,"path":380,"stem":381,"access":12},"drive","\u002Ffamilies\u002Fto\u002Fdrive","families\u002Fto\u002Fdrive",{"title":383,"path":384,"stem":385,"access":12},"file","\u002Ffamilies\u002Fto\u002Ffile","families\u002Fto\u002Ffile",{"title":387,"path":388,"stem":389,"access":12},"icloud","\u002Ffamilies\u002Fto\u002Ficloud","families\u002Fto\u002Ficloud",{"title":391,"path":392,"stem":393,"access":12},"memory","\u002Ffamilies\u002Fto\u002Fmemory","families\u002Fto\u002Fmemory",{"title":395,"path":396,"stem":397,"access":12},"meter","\u002Ffamilies\u002Fto\u002Fmeter","families\u002Fto\u002Fmeter",{"title":399,"path":400,"stem":401,"access":12},"mysql","\u002Ffamilies\u002Fto\u002Fmysql","families\u002Fto\u002Fmysql",{"title":403,"path":404,"stem":405,"access":12},"nfs","\u002Ffamilies\u002Fto\u002Fnfs","families\u002Fto\u002Fnfs",{"title":407,"path":408,"stem":409,"access":12},"postgres","\u002Ffamilies\u002Fto\u002Fpostgres","families\u002Fto\u002Fpostgres",{"title":411,"path":412,"stem":413,"access":12},"probe","\u002Ffamilies\u002Fto\u002Fprobe","families\u002Fto\u002Fprobe",{"title":263,"path":415,"stem":416,"access":12},"\u002Ffamilies\u002Fto\u002Frest","families\u002Fto\u002Frest",{"title":418,"path":419,"stem":420,"access":12},"smb","\u002Ffamilies\u002Fto\u002Fsmb","families\u002Fto\u002Fsmb",{"title":422,"path":423,"stem":424,"access":12},"sqlite","\u002Ffamilies\u002Fto\u002Fsqlite","families\u002Fto\u002Fsqlite",{"title":426,"path":427,"stem":428,"access":12},"ssh","\u002Ffamilies\u002Fto\u002Fssh","families\u002Fto\u002Fssh",{"title":430,"path":431,"stem":432,"access":12,"icon":433},"Store-locator descriptors","\u002Ffamilies\u002Fto\u002Fstore-locator","families\u002Fto\u002Fstore-locator","i-lucide-map-pin",{"title":435,"path":436,"stem":437,"access":12},"supabase","\u002Ffamilies\u002Fto\u002Fsupabase","families\u002Fto\u002Fsupabase",{"title":439,"path":440,"stem":441,"access":12},"turso","\u002Ffamilies\u002Fto\u002Fturso","families\u002Fto\u002Fturso",{"title":443,"path":444,"stem":445,"access":12},"webdav","\u002Ffamilies\u002Fto\u002Fwebdav","families\u002Fto\u002Fwebdav",{"title":447,"path":448,"stem":449,"children":450},"Faq","\u002Ffaq","faq",[451,454,458,462,466,470],{"title":10,"path":448,"stem":452,"access":12,"icon":453},"faq\u002Findex","i-lucide-circle-help",{"title":455,"path":456,"stem":457,"access":12},"Where can noy-db run?","\u002Ffaq\u002Fwhere-can-noy-db-run","faq\u002F1.where-can-noy-db-run",{"title":459,"path":460,"stem":461,"access":12},"Native mobile & PWA","\u002Ffaq\u002Fmobile-and-pwa","faq\u002F2.mobile-and-pwa",{"title":463,"path":464,"stem":465,"access":12},"Threat model","\u002Ffaq\u002Fthreat-model","faq\u002F3.threat-model",{"title":467,"path":468,"stem":469,"access":12},"Community & ecosystem","\u002Ffaq\u002Fcommunity-and-ecosystem","faq\u002F4.community-and-ecosystem",{"title":471,"path":472,"stem":473,"access":12},"Licensing","\u002Ffaq\u002Flicensing","faq\u002F5.licensing",{"title":475,"path":476,"stem":477,"children":478},"Glossary","\u002Fglossary","glossary",[479,482,486,490,494],{"title":10,"path":476,"stem":480,"access":12,"icon":481},"glossary\u002Findex","i-lucide-book-a",{"title":483,"path":484,"stem":485,"access":12},"Core vocabulary","\u002Fglossary\u002Fcore-vocabulary","glossary\u002Fcore-vocabulary",{"title":487,"path":488,"stem":489,"access":12},"Deletion vocabulary","\u002Fglossary\u002Fdeletion","glossary\u002Fdeletion",{"title":491,"path":492,"stem":493,"access":12},"Satellite collection vocabulary","\u002Fglossary\u002Fsatellite-collections","glossary\u002Fsatellite-collections",{"title":495,"path":496,"stem":497,"access":12},"Schema migration vocabulary","\u002Fglossary\u002Fschema-migration","glossary\u002Fschema-migration",{"title":499,"path":500,"stem":501,"children":502},"Recipes","\u002Frecipes","recipes",[503,506,510,514,518,522,526,530,534,538,542,546,550,554,558,562,567,571,575,580,584,588,592],{"title":10,"path":500,"stem":504,"access":12,"icon":505},"recipes\u002Findex","i-lucide-play",{"title":507,"path":508,"stem":509,"access":12},"Accounting app","\u002Frecipes\u002Faccounting-app","recipes\u002Faccounting-app",{"title":511,"path":512,"stem":513,"access":12},"Analytics app","\u002Frecipes\u002Fanalytics-app","recipes\u002Fanalytics-app",{"title":515,"path":516,"stem":517,"access":12},"Attestation verifier","\u002Frecipes\u002Fattestation-verifier","recipes\u002Fattestation-verifier",{"title":519,"path":520,"stem":521,"access":12},"AWS-KMS attestation","\u002Frecipes\u002Faws-kms-pdf-attestation","recipes\u002Faws-kms-pdf-attestation",{"title":523,"path":524,"stem":525,"access":12},"Browser durability","\u002Frecipes\u002Fbrowser-durability","recipes\u002Fbrowser-durability",{"title":527,"path":528,"stem":529,"access":12},"Production cloud store","\u002Frecipes\u002Fcloud-store","recipes\u002Fcloud-store",{"title":531,"path":532,"stem":533,"access":12},"Compose your own setup","\u002Frecipes\u002Fcompose-your-own","recipes\u002Fcompose-your-own",{"title":535,"path":536,"stem":537,"access":12},"Confidential HR records","\u002Frecipes\u002Fconfidential-hr","recipes\u002Fconfidential-hr",{"title":539,"path":540,"stem":541,"access":12},"Composing an echo secret","\u002Frecipes\u002Fecho-secret-enrollment","recipes\u002Fecho-secret-enrollment",{"title":543,"path":544,"stem":545,"access":12},"Email archive","\u002Frecipes\u002Femail-archive","recipes\u002Femail-archive",{"title":547,"path":548,"stem":549,"access":12},"Email archive — threads & revisions","\u002Frecipes\u002Femail-archive-threads-revisions","recipes\u002Femail-archive-threads-revisions",{"title":551,"path":552,"stem":553,"access":12},"Export & portability","\u002Frecipes\u002Fexport-portability","recipes\u002Fexport-portability",{"title":555,"path":556,"stem":557,"access":12},"Passwordless OIDC (AWS Cognito)","\u002Frecipes\u002Fpasswordless-oidc","recipes\u002Fpasswordless-oidc",{"title":559,"path":560,"stem":561,"access":12},"Personal notebook","\u002Frecipes\u002Fpersonal-notebook","recipes\u002Fpersonal-notebook",{"title":563,"path":564,"stem":565,"access":12,"icon":566},"Playgrounds","\u002Frecipes\u002Fplaygrounds","recipes\u002Fplaygrounds","i-lucide-gamepad-2",{"title":568,"path":569,"stem":570,"access":12},"React invoice app","\u002Frecipes\u002Freact-invoices","recipes\u002Freact-invoices",{"title":572,"path":573,"stem":574,"access":12},"Collaborative app","\u002Frecipes\u002Frealtime-crdt-app","recipes\u002Frealtime-crdt-app",{"title":576,"path":577,"stem":578,"access":12,"icon":579},"Run the showcases","\u002Frecipes\u002Frun-the-showcases","recipes\u002Frun-the-showcases","i-lucide-flask-conical",{"title":581,"path":582,"stem":583,"access":12},"Search & RAG","\u002Frecipes\u002Fsearch-rag","recipes\u002Fsearch-rag",{"title":585,"path":586,"stem":587,"access":12},"Team vault topology","\u002Frecipes\u002Fteam-vault-topology","recipes\u002Fteam-vault-topology",{"title":589,"path":590,"stem":591,"access":12},"Unlock ceremonies","\u002Frecipes\u002Funlock-ceremonies","recipes\u002Funlock-ceremonies",{"title":593,"path":594,"stem":595,"access":12},"User preferences","\u002Frecipes\u002Fuser-preferences","recipes\u002Fuser-preferences",{"title":597,"path":598,"stem":599,"children":600},"Services","\u002Fservices","services",[601,604,608,612,616,620,624,628,632,636,640,644,648,652,656,660,664,668,672,676,680,684,688,692,696,700,704,710,714,718,722,726,730,734,738,742,746,750,755,759,763,767,771,775,779,783,787,791,795,799,803,807,811,815,819,823,827,831,835,839,843,847,851,855,859,863,867,871,875,879,883,887,891,895,899],{"title":10,"path":598,"stem":602,"access":12,"icon":603},"services\u002Findex","i-lucide-layers",{"title":605,"path":606,"stem":607,"access":12},"archive","\u002Fservices\u002Farchive","services\u002Farchive",{"title":609,"path":610,"stem":611,"access":12},"attestation","\u002Fservices\u002Fattestation","services\u002Fattestation",{"title":613,"path":614,"stem":615,"access":12},"auth-introspection","\u002Fservices\u002Fauth-introspection","services\u002Fauth-introspection",{"title":617,"path":618,"stem":619,"access":12},"auth-landscape","\u002Fservices\u002Fauth-landscape","services\u002Fauth-landscape",{"title":621,"path":622,"stem":623,"access":12},"blobs","\u002Fservices\u002Fblobs","services\u002Fblobs",{"title":625,"path":626,"stem":627,"access":12},"broker","\u002Fservices\u002Fbroker","services\u002Fbroker",{"title":629,"path":630,"stem":631,"access":12},"bundle","\u002Fservices\u002Fbundle","services\u002Fbundle",{"title":633,"path":634,"stem":635,"access":12},"classified","\u002Fservices\u002Fclassified","services\u002Fclassified",{"title":637,"path":638,"stem":639,"access":12},"computed","\u002Fservices\u002Fcomputed","services\u002Fcomputed",{"title":641,"path":642,"stem":643,"access":12},"consent","\u002Fservices\u002Fconsent","services\u002Fconsent",{"title":645,"path":646,"stem":647,"access":12},"cover","\u002Fservices\u002Fcover","services\u002Fcover",{"title":649,"path":650,"stem":651,"access":12},"coverage","\u002Fservices\u002Fcoverage","services\u002Fcoverage",{"title":653,"path":654,"stem":655,"access":12},"crdt","\u002Fservices\u002Fcrdt","services\u002Fcrdt",{"title":657,"path":658,"stem":659,"access":12},"cross-join","\u002Fservices\u002Fcross-join","services\u002Fcross-join",{"title":661,"path":662,"stem":663,"access":12},"custody","\u002Fservices\u002Fcustody","services\u002Fcustody",{"title":665,"path":666,"stem":667,"access":12},"derivations","\u002Fservices\u002Fderivations","services\u002Fderivations",{"title":669,"path":670,"stem":671,"access":12},"directory","\u002Fservices\u002Fdirectory","services\u002Fdirectory",{"title":673,"path":674,"stem":675,"access":12},"search — AI","\u002Fservices\u002Fembeddings","services\u002Fembeddings",{"title":677,"path":678,"stem":679,"access":12},"Field metadata","\u002Fservices\u002Ffield-metadata","services\u002Ffield-metadata",{"title":681,"path":682,"stem":683,"access":12},"Foreign refs","\u002Fservices\u002Fforeign-refs","services\u002Fforeign-refs",{"title":685,"path":686,"stem":687,"access":12},"forget","\u002Fservices\u002Fforget","services\u002Fforget",{"title":689,"path":690,"stem":691,"access":12},"governance","\u002Fservices\u002Fgovernance","services\u002Fgovernance",{"title":693,"path":694,"stem":695,"access":12},"guards","\u002Fservices\u002Fguards","services\u002Fguards",{"title":697,"path":698,"stem":699,"access":12},"history","\u002Fservices\u002Fhistory","services\u002Fhistory",{"title":701,"path":702,"stem":703,"access":12},"i18n","\u002Fservices\u002Fi18n","services\u002Fi18n",{"title":705,"path":706,"stem":707,"children":708,"access":12},"indexing","\u002Fservices\u002Findexing","services\u002Findexing",[709],{"title":705,"path":706,"stem":707,"access":12},{"title":711,"path":712,"stem":713,"access":12},"introspection","\u002Fservices\u002Fintrospection","services\u002Fintrospection",{"title":715,"path":716,"stem":717,"access":12},"joins","\u002Fservices\u002Fjoins","services\u002Fjoins",{"title":719,"path":720,"stem":721,"access":12},"lazy","\u002Fservices\u002Flazy","services\u002Flazy",{"title":723,"path":724,"stem":725,"access":12},"links","\u002Fservices\u002Flinks","services\u002Flinks",{"title":727,"path":728,"stem":729,"access":12},"live","\u002Fservices\u002Flive","services\u002Flive",{"title":731,"path":732,"stem":733,"access":12},"lookup","\u002Fservices\u002Flookup","services\u002Flookup",{"title":735,"path":736,"stem":737,"access":12},"manifest","\u002Fservices\u002Fmanifest","services\u002Fmanifest",{"title":739,"path":740,"stem":741,"access":12},"materialized-views","\u002Fservices\u002Fmaterialized-views","services\u002Fmaterialized-views",{"title":743,"path":744,"stem":745,"access":12},"Metadata ladder","\u002Fservices\u002Fmetadata-ladder","services\u002Fmetadata-ladder",{"title":747,"path":748,"stem":749,"access":12},"Metadata riders","\u002Fservices\u002Fmetadata-riders","services\u002Fmetadata-riders",{"title":751,"path":752,"stem":753,"access":12,"badge":754},"metrics","\u002Fservices\u002Fmetrics","services\u002Fmetrics","Planned",{"title":756,"path":757,"stem":758,"access":12,"badge":754},"migrations","\u002Fservices\u002Fmigrations","services\u002Fmigrations",{"title":760,"path":761,"stem":762,"access":12},"money","\u002Fservices\u002Fmoney","services\u002Fmoney",{"title":764,"path":765,"stem":766,"access":12},"numbering","\u002Fservices\u002Fnumbering","services\u002Fnumbering",{"title":768,"path":769,"stem":770,"access":12},"overlay-views","\u002Fservices\u002Foverlay-views","services\u002Foverlay-views",{"title":772,"path":773,"stem":774,"access":12,"badge":754},"partitioning","\u002Fservices\u002Fpartitioning","services\u002Fpartitioning",{"title":776,"path":777,"stem":778,"access":12},"periods","\u002Fservices\u002Fperiods","services\u002Fperiods",{"title":780,"path":781,"stem":782,"access":12},"persisted-schemas","\u002Fservices\u002Fpersisted-schemas","services\u002Fpersisted-schemas",{"title":784,"path":785,"stem":786,"access":12},"plaintext-bypass","\u002Fservices\u002Fplaintext-bypass","services\u002Fplaintext-bypass",{"title":788,"path":789,"stem":790,"access":12},"pod-redirect","\u002Fservices\u002Fpod-redirect","services\u002Fpod-redirect",{"title":792,"path":793,"stem":794,"access":12},"pod-signature","\u002Fservices\u002Fpod-signature","services\u002Fpod-signature",{"title":796,"path":797,"stem":798,"access":12},"policy","\u002Fservices\u002Fpolicy","services\u002Fpolicy",{"title":800,"path":801,"stem":802,"access":12},"portability","\u002Fservices\u002Fportability","services\u002Fportability",{"title":804,"path":805,"stem":806,"access":12},"reduce","\u002Fservices\u002Freduce","services\u002Freduce",{"title":808,"path":809,"stem":810,"access":12},"routing","\u002Fservices\u002Frouting","services\u002Frouting",{"title":812,"path":813,"stem":814,"access":12},"satellites","\u002Fservices\u002Fsatellites","services\u002Fsatellites",{"title":816,"path":817,"stem":818,"access":12},"schema-identifiers","\u002Fservices\u002Fschema-identifiers","services\u002Fschema-identifiers",{"title":820,"path":821,"stem":822,"access":12},"schema-update","\u002Fservices\u002Fschema-update","services\u002Fschema-update",{"title":824,"path":825,"stem":826,"access":12},"sealed-record","\u002Fservices\u002Fsealed-record","services\u002Fsealed-record",{"title":828,"path":829,"stem":830,"access":12},"sealing provider id","\u002Fservices\u002Fsealing-pid-stability","services\u002Fsealing-pid-stability",{"title":832,"path":833,"stem":834,"access":12},"search — full-text","\u002Fservices\u002Fsearch","services\u002Fsearch",{"title":836,"path":837,"stem":838,"access":12},"sequence","\u002Fservices\u002Fsequence","services\u002Fsequence",{"title":840,"path":841,"stem":842,"access":12},"session","\u002Fservices\u002Fsession","services\u002Fsession",{"title":844,"path":845,"stem":846,"access":12},"session-tiers","\u002Fservices\u002Fsession-tiers","services\u002Fsession-tiers",{"title":848,"path":849,"stem":850,"access":12},"shadow","\u002Fservices\u002Fshadow","services\u002Fshadow",{"title":852,"path":853,"stem":854,"access":12},"share-link","\u002Fservices\u002Fshare-link","services\u002Fshare-link",{"title":856,"path":857,"stem":858,"access":12},"snapshots","\u002Fservices\u002Fsnapshots","services\u002Fsnapshots",{"title":860,"path":861,"stem":862,"access":12},"store capabilities","\u002Fservices\u002Fstorage-capability-matrix","services\u002Fstorage-capability-matrix",{"title":864,"path":865,"stem":866,"access":12},"sync","\u002Fservices\u002Fsync","services\u002Fsync",{"title":868,"path":869,"stem":870,"access":12},"team","\u002Fservices\u002Fteam","services\u002Fteam",{"title":872,"path":873,"stem":874,"access":12},"tiers","\u002Fservices\u002Ftiers","services\u002Ftiers",{"title":876,"path":877,"stem":878,"access":12},"transactions","\u002Fservices\u002Ftransactions","services\u002Ftransactions",{"title":880,"path":881,"stem":882,"access":12},"partitions","\u002Fservices\u002Ftransferable-partitions","services\u002Ftransferable-partitions",{"title":884,"path":885,"stem":886,"access":12},"user-envelope","\u002Fservices\u002Fuser-envelope","services\u002Fuser-envelope",{"title":888,"path":889,"stem":890,"access":12,"badge":754},"validation","\u002Fservices\u002Fvalidation","services\u002Fvalidation",{"title":892,"path":893,"stem":894,"access":12},"vault-head","\u002Fservices\u002Fvault-head","services\u002Fvault-head",{"title":896,"path":897,"stem":898,"access":12},"via","\u002Fservices\u002Fvia","services\u002Fvia",{"title":900,"path":901,"stem":902,"access":12},"withdrawal","\u002Fservices\u002Fwithdrawal","services\u002Fwithdrawal",{"title":904,"path":905,"stem":906,"children":907},"Start","\u002Fstart","start",[908,911,915,919,923],{"title":10,"path":905,"stem":909,"access":12,"icon":910},"start\u002Findex","i-lucide-flag",{"title":912,"path":913,"stem":914,"access":12},"Install","\u002Fstart\u002Finstall","start\u002F1.install",{"title":916,"path":917,"stem":918,"access":12},"Your first vault","\u002Fstart\u002Ffirst-vault","start\u002F2.first-vault",{"title":920,"path":921,"stem":922,"access":12},"Your first service","\u002Fstart\u002Ffirst-service","start\u002F3.first-service",{"title":924,"path":925,"stem":926,"access":12},"See the ciphertext","\u002Fstart\u002Fsee-the-ciphertext","start\u002F4.see-the-ciphertext",{"id":928,"title":844,"access":12,"apiToc":929,"body":930,"category":8297,"description":8298,"extension":8299,"meta":8300,"navigation":8297,"nest":8301,"path":845,"seo":8302,"sinceVersion":8297,"status":8303,"stem":846,"subtitle":8297,"tier":8304,"tocExternal":8297,"__hash__":8305},"docs\u002Fservices\u002Fsession-tiers.md",false,{"type":931,"value":932,"toc":8241},"minimark",[933,936,969,973,978,1007,1010,1026,1029,1033,1048,1143,1168,1287,1291,1412,1423,1426,1460,1464,1467,1472,1475,1486,1562,1572,1577,1584,1665,1673,1697,1701,1814,1817,1870,1912,1916,1919,1938,1942,1952,2221,2225,2306,2310,2326,2650,2654,2664,2859,2866,2870,2888,3093,3108,3112,3115,3295,3298,3302,3560,3564,3792,3796,3810,3814,3818,3940,3951,3961,4062,4066,4076,4158,4264,4270,4346,4361,4365,4373,4383,4389,4411,4418,4421,4425,4637,4670,4673,4736,4739,4743,4749,4820,4951,4975,4979,5100,5120,5127,5282,5315,5319,5326,5430,5434,5441,5508,5512,5515,5595,5608,5764,5784,5794,5890,5904,5908,5922,6184,6215,6222,6258,6262,6274,6465,6492,6495,6570,6574,6692,6695,6705,6709,6712,6933,6937,6989,6995,7083,7086,7089,7092,7096,7190,7193,7220,7224,7313,7315,7335,7339,7432,7436,7488,7490,7540,7544,7691,7700,7743,7746,7779,7781,7847,7851,7899,7901,7915,7919,8078,8082,8181,8185,8237],[934,935,844],"h1",{"id":844},[937,938,939],"blockquote",{},[940,941,942,946,947,951,952,955,956,960,961,964,965,968],"p",{},[943,944,945],"strong",{},"Session tiers, not record tiers."," This page covers session ",[948,949,950],"em",{},"authentication"," tiers\n(how far a user has unlocked). For per-record ",[948,953,954],{},"data"," tiers — ",[957,958,959],"code",{},"elevate","\u002F",[957,962,963],{},"demote",",\ntier DEKs, tiered blobs — see ",[966,967,872],"a",{"href":873},".",[970,971],"service-meta",{":rows":972},"[{\"label\":\"Subpath\",\"value\":\"`@noy-db\u002Fhub` (core) — session tiers, policy gates, and secret lifecycle ship in `@noy-db\u002Fhub`'s main entry point; tier-2\u002F3 factor packages are the `@noy-db\u002Fon-*` family\"},{\"label\":\"Factory\",\"value\":\"none — always on for every `createNoydb()` call\"},{\"label\":\"Cluster\",\"value\":\"collaboration-and-auth\"},{\"label\":\"Cross-cuts\",\"value\":\"every `@noy-db\u002Fon-*` unlock package, plus `@noy-db\u002Fhub`'s KEK derivation, keyring layout, and session module.\"}]",[974,975,977],"h2",{"id":976},"what-it-is","What it is",[940,979,980,981,984,985,989,990,993,994,998,999,1002,1003,1006],{},"Every noy-db vault is unlocked with a ",[943,982,983],{},"secret"," — the one secret the ",[966,986,988],{"href":987},"..\u002Fcore\u002Fnoydb\u002Fenclave","hub"," never sees in plaintext outside a short-lived in-memory derivation. (The hub is the trust-boundary module — ",[957,991,992],{},"@noy-db\u002Fhub"," — that owns encryption and the ",[966,995,997],{"href":996},"..\u002Fcore\u002F06-permissions-and-keyring","keyring","; storage backends only ever see ciphertext.) Typing that secret on every read is not realistic, so noy-db layers two lighter-weight unlock methods on top of it and calls the resulting stack the ",[943,1000,1001],{},"three-tier privilege ladder",". Sensitive actions (rotating the secret, enrolling a new device, exporting plaintext, granting a teammate access, …) are guarded by a ",[943,1004,1005],{},"policy gate",": a small declarative rule that says which tier — and which extra proof — an action requires.",[940,1008,1009],{},"Two terms recur throughout this page:",[1011,1012,1013,1020],"ul",{},[1014,1015,1016,1019],"li",{},[943,1017,1018],{},"KEK"," (key-encryption key) — the key derived from the secret. It never touches disk; its only job is to unwrap the DEKs.",[1014,1021,1022,1025],{},[943,1023,1024],{},"DEK"," (data-encryption key) — one per collection. DEKs are what actually encrypt your records with AES-256-GCM; the KEK just wraps and unwraps them.",[940,1027,1028],{},"The ladder exists to satisfy three constraints at once: the vault owner controls the root secret and nobody else can read it (zero-knowledge); routine use shouldn't require retyping a secret; and losing a device, a passkey, or the secret itself each has a recovery path that doesn't collapse the security model.",[974,1030,1032],{"id":1031},"opt-in","Opt in",[940,1034,1035,1036,1039,1040,1043,1044,1047],{},"Session tiers require no opt-in. Every ",[957,1037,1038],{},"createNoydb()"," call gets the three-tier ladder and a default policy (",[957,1041,1042],{},"PERSONAL_POLICY",") automatically — there is no ",[957,1045,1046],{},"with*()"," strategy to wire in for tier 1.",[1049,1050,1055],"pre",{"className":1051,"code":1052,"language":1053,"meta":1054,"style":1054},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { createNoydb } from '@noy-db\u002Fhub'\n\n\u002F\u002F Tier 1 (secret) and the default policy are already active — nothing to opt into.\nconst db = await createNoydb({ store, user })\n","ts","",[957,1056,1057,1089,1096,1103],{"__ignoreMap":1054},[1058,1059,1062,1066,1070,1074,1077,1080,1083,1086],"span",{"class":1060,"line":1061},"line",1,[1058,1063,1065],{"class":1064},"s7zQu","import",[1058,1067,1069],{"class":1068},"sMK4o"," {",[1058,1071,1073],{"class":1072},"sTEyZ"," createNoydb",[1058,1075,1076],{"class":1068}," }",[1058,1078,1079],{"class":1064}," from",[1058,1081,1082],{"class":1068}," '",[1058,1084,992],{"class":1085},"sfazB",[1058,1087,1088],{"class":1068},"'\n",[1058,1090,1092],{"class":1060,"line":1091},2,[1058,1093,1095],{"emptyLinePlaceholder":1094},true,"\n",[1058,1097,1099],{"class":1060,"line":1098},3,[1058,1100,1102],{"class":1101},"sHwdD","\u002F\u002F Tier 1 (secret) and the default policy are already active — nothing to opt into.\n",[1058,1104,1106,1110,1113,1116,1119,1122,1125,1128,1131,1134,1137,1140],{"class":1060,"line":1105},4,[1058,1107,1109],{"class":1108},"spNyl","const",[1058,1111,1112],{"class":1072}," db ",[1058,1114,1115],{"class":1068},"=",[1058,1117,1118],{"class":1064}," await",[1058,1120,1073],{"class":1121},"s2Zo4",[1058,1123,1124],{"class":1072},"(",[1058,1126,1127],{"class":1068},"{",[1058,1129,1130],{"class":1072}," store",[1058,1132,1133],{"class":1068},",",[1058,1135,1136],{"class":1072}," user ",[1058,1138,1139],{"class":1068},"}",[1058,1141,1142],{"class":1072},")\n",[940,1144,1145,1146,1149,1150,1153,1154,1158,1159,1163,1164,1167],{},"Tier 2 and tier 3 are different: each method lives in its own ",[957,1147,1148],{},"@noy-db\u002Fon-*"," package, and enrolling a slot means importing that package's enrollment function and calling the matching ",[957,1151,1152],{},"db.enroll*"," method (see ",[966,1155,1157],{"href":1156},"#tier-2-authenticate-multi-slot","Tier 2"," and ",[966,1160,1162],{"href":1161},"#tier-3-unlock-quick-resume","Tier 3"," below). For example, tier 3 via ",[957,1165,1166],{},"@noy-db\u002Fon-pin",":",[1049,1169,1171],{"className":1051,"code":1170,"language":1053,"meta":1054,"style":1054},"import { enrollPin } from '@noy-db\u002Fon-pin'\n\nconst pinState = await enrollPin(unlockedKeyring, { pin: '1234', ttlMs: 15 * 60 * 1000 })\nawait db.team.enrollUnlock('acme', pinState)\n",[957,1172,1173,1192,1196,1256],{"__ignoreMap":1054},[1058,1174,1175,1177,1179,1182,1184,1186,1188,1190],{"class":1060,"line":1061},[1058,1176,1065],{"class":1064},[1058,1178,1069],{"class":1068},[1058,1180,1181],{"class":1072}," enrollPin",[1058,1183,1076],{"class":1068},[1058,1185,1079],{"class":1064},[1058,1187,1082],{"class":1068},[1058,1189,1166],{"class":1085},[1058,1191,1088],{"class":1068},[1058,1193,1194],{"class":1060,"line":1091},[1058,1195,1095],{"emptyLinePlaceholder":1094},[1058,1197,1198,1200,1203,1205,1207,1209,1212,1214,1216,1220,1222,1224,1227,1230,1232,1235,1237,1241,1244,1247,1249,1252,1254],{"class":1060,"line":1098},[1058,1199,1109],{"class":1108},[1058,1201,1202],{"class":1072}," pinState ",[1058,1204,1115],{"class":1068},[1058,1206,1118],{"class":1064},[1058,1208,1181],{"class":1121},[1058,1210,1211],{"class":1072},"(unlockedKeyring",[1058,1213,1133],{"class":1068},[1058,1215,1069],{"class":1068},[1058,1217,1219],{"class":1218},"swJcz"," pin",[1058,1221,1167],{"class":1068},[1058,1223,1082],{"class":1068},[1058,1225,1226],{"class":1085},"1234",[1058,1228,1229],{"class":1068},"'",[1058,1231,1133],{"class":1068},[1058,1233,1234],{"class":1218}," ttlMs",[1058,1236,1167],{"class":1068},[1058,1238,1240],{"class":1239},"sbssI"," 15",[1058,1242,1243],{"class":1068}," *",[1058,1245,1246],{"class":1239}," 60",[1058,1248,1243],{"class":1068},[1058,1250,1251],{"class":1239}," 1000",[1058,1253,1076],{"class":1068},[1058,1255,1142],{"class":1072},[1058,1257,1258,1261,1264,1266,1268,1270,1273,1275,1277,1280,1282,1284],{"class":1060,"line":1105},[1058,1259,1260],{"class":1064},"await",[1058,1262,1263],{"class":1072}," db",[1058,1265,968],{"class":1068},[1058,1267,868],{"class":1072},[1058,1269,968],{"class":1068},[1058,1271,1272],{"class":1121},"enrollUnlock",[1058,1274,1124],{"class":1072},[1058,1276,1229],{"class":1068},[1058,1278,1279],{"class":1085},"acme",[1058,1281,1229],{"class":1068},[1058,1283,1133],{"class":1068},[1058,1285,1286],{"class":1072}," pinState)\n",[974,1288,1290],{"id":1289},"the-three-tiers","The three tiers",[1292,1293,1294,1316],"table",{},[1295,1296,1297],"thead",{},[1298,1299,1300,1304,1307,1310,1313],"tr",{},[1301,1302,1303],"th",{},"Tier",[1301,1305,1306],{},"Name",[1301,1308,1309],{},"Role",[1301,1311,1312],{},"Typical lifetime",[1301,1314,1315],{},"Where the factor lives",[1317,1318,1319,1353,1388],"tbody",{},[1298,1320,1321,1327,1333,1343,1346],{},[1322,1323,1324],"td",{},[943,1325,1326],{},"1",[1322,1328,1329,1332],{},[943,1330,1331],{},"Secret"," (root)",[1322,1334,1335,1336,1338,1339,1342],{},"Derives the KEK via PBKDF2. Built into ",[957,1337,992],{}," core — there is no ",[957,1340,1341],{},"on-secret"," package.",[1322,1344,1345],{},"Touched only at enrollment, recovery, and tier-1-gated actions.",[1322,1347,1348,1349,1352],{},"The user's memory (standard and echo modes) or a developer-supplied ",[957,1350,1351],{},"NoydbSealer"," (managed mode).",[1298,1354,1355,1360,1365,1372,1375],{},[1322,1356,1357],{},[943,1358,1359],{},"2",[1322,1361,1362],{},[943,1363,1364],{},"Authenticate",[1322,1366,1367,1368,1371],{},"Routine login — replaces retyping the secret. ",[943,1369,1370],{},"Multi-slot",": any one enrolled method unlocks.",[1322,1373,1374],{},"Days to weeks, app-defined.",[1322,1376,1377,1380,1381,1380,1384,1387],{},[957,1378,1379],{},"on-webauthn"," \u002F ",[957,1382,1383],{},"on-oidc",[957,1385,1386],{},"on-password"," slots inside the keyring file.",[1298,1389,1390,1395,1400,1403,1406],{},[1322,1391,1392],{},[943,1393,1394],{},"3",[1322,1396,1397],{},[943,1398,1399],{},"Unlock",[1322,1401,1402],{},"Quick-resume after idle.",[1322,1404,1405],{},"Minutes (e.g. 15 min), app-defined.",[1322,1407,1408,1411],{},[957,1409,1410],{},"on-pin"," — the canonical tier-3 primitive.",[940,1413,1414,1415,1418,1419,1422],{},"Tier 1 outranks tier 2, which outranks tier 3: only a higher tier can reset a lower one, and a policy gate's ",[957,1416,1417],{},"minTier"," is a ",[948,1420,1421],{},"floor"," — a tier-1 session can always do what a tier-2 gate allows, never the reverse.",[940,1424,1425],{},"Two auxiliary mechanisms sit alongside the ladder rather than inside it:",[1011,1427,1428,1451],{},[1014,1429,1430,1433,1434,1437,1438,1437,1441,1437,1444,1447,1448,1450],{},[943,1431,1432],{},"2FA factors"," — ",[957,1435,1436],{},"on-totp",", ",[957,1439,1440],{},"on-email-otp",[957,1442,1443],{},"on-recovery",[957,1445,1446],{},"on-shamir",", and roaming ",[957,1449,1379],{}," supply fresh, off-device proofs that policy gates can additionally demand for sensitive actions. They are not session tiers; a fresh proof is required every time a gate asks for one.",[1014,1452,1453,1433,1456,1459],{},[943,1454,1455],{},"Threat overlay",[957,1457,1458],{},"on-threat"," layers lockout, duress-secret wipe, and honeypot routing on top of tier-1\u002Ftier-2 attempts, independently of the policy gates.",[974,1461,1463],{"id":1462},"usage","Usage",[940,1465,1466],{},"Each tier below is a self-contained flow: enroll a factor, then unlock or act with it. Policy gates, secret rotation\u002Frecovery, and managed-secret mode build on top of the three tiers and are covered in their own subsections.",[1468,1469,1471],"h3",{"id":1470},"tier-1-secret","Tier 1 — Secret",[940,1473,1474],{},"The secret is the only secret the user genuinely owns. The hub derives the KEK from it via PBKDF2-SHA256 with 600,000 iterations. The KDF, wrap algorithm (AES-KW), envelope format, and salt\u002FIV sizes are fixed inside the hub — there are no options to change them.",[940,1476,1477,1478,1481,1482,1485],{},"Tier 1 ships in ",[943,1479,1480],{},"three modes",". You pick one per vault with ",[957,1483,1484],{},"secretMode","; the choice is recorded in the keyring, so the data itself decides how it may be opened — not the app that happens to be reading it.",[1292,1487,1488,1505],{},[1295,1489,1490],{},[1298,1491,1492,1495,1499,1502],{},[1301,1493,1494],{},"Mode",[1301,1496,1497],{},[957,1498,1484],{},[1301,1500,1501],{},"What the user supplies",[1301,1503,1504],{},"Pick it when",[1317,1506,1507,1526,1544],{},[1298,1508,1509,1515,1520,1523],{},[1322,1510,1511,1514],{},[943,1512,1513],{},"standard"," (default)",[1322,1516,1517],{},[957,1518,1519],{},"'standard'",[1322,1521,1522],{},"one phrase, typed into one field",[1322,1524,1525],{},"The ordinary case: a phrase the user remembers, typed rarely.",[1298,1527,1528,1533,1538,1541],{},[1322,1529,1530],{},[943,1531,1532],{},"echo",[1322,1534,1535],{},[957,1536,1537],{},"'echo'",[1322,1539,1540],{},"one sentence enrolled as three parts (prompt \u002F echo \u002F key)",[1322,1542,1543],{},"You want the rare tier-1 prompt to be phishing-resistant, and a built-in memory cue for a secret the user types twice a year.",[1298,1545,1546,1551,1556,1559],{},[1322,1547,1548],{},[943,1549,1550],{},"managed",[1322,1552,1553],{},[957,1554,1555],{},"'managed'",[1322,1557,1558],{},"nothing — the hub generates and seals the secret",[1322,1560,1561],{},"You want coercion resistance: the user has no secret to reveal.",[940,1563,1564,1565,1158,1568,1571],{},"The rest of this subsection describes standard mode; ",[966,1566,1532],{"href":1567},"#tier-1-echo-secret",[966,1569,1550],{"href":1570},"#managed-secret-mode"," have their own subsections below. All three derive the same KEK and feed the same tier-2\u002Ftier-3 ladder — the mode changes only how tier 1 is presented and proven.",[1573,1574,1576],"h4",{"id":1575},"phrase-format","Phrase format",[940,1578,1579,1580,1583],{},"Secrets are ",[943,1581,1582],{},"phrases",": several simple lowercase words, easy to remember, constrained enough that a weak choice can't silently collapse the security floor.",[1292,1585,1586,1599],{},[1295,1587,1588],{},[1298,1589,1590,1593,1596],{},[1301,1591,1592],{},"Rule",[1301,1594,1595],{},"Default",[1301,1597,1598],{},"Strict policy",[1317,1600,1601,1615,1625,1634,1645,1655],{},[1298,1602,1603,1606,1612],{},[1322,1604,1605],{},"Character pattern",[1322,1607,1608,1611],{},[957,1609,1610],{},"\u002F^[a-z]+( [a-z]+)*$\u002F"," (lowercase letters and single spaces only)",[1322,1613,1614],{},"same",[1298,1616,1617,1620,1623],{},[1322,1618,1619],{},"No punctuation, symbols, digits",[1322,1621,1622],{},"required",[1322,1624,1622],{},[1298,1626,1627,1630,1632],{},[1322,1628,1629],{},"No leading\u002Ftrailing or doubled whitespace",[1322,1631,1622],{},[1322,1633,1622],{},[1298,1635,1636,1639,1642],{},[1322,1637,1638],{},"Minimum word count",[1322,1640,1641],{},"6 (~77 bits)",[1322,1643,1644],{},"8 (~103 bits)",[1298,1646,1647,1650,1653],{},[1322,1648,1649],{},"Minimum word length",[1322,1651,1652],{},"3 characters",[1322,1654,1652],{},[1298,1656,1657,1660,1663],{},[1322,1658,1659],{},"Repeated adjacent words",[1322,1661,1662],{},"rejected",[1322,1664,1662],{},[1049,1666,1671],{"className":1667,"code":1669,"language":1670},[1668],"language-text","✅ \"correct horse battery staple printer toaster\"\n❌ \"abc\"                                              — too few words\n❌ \"Correct horse battery staple printer toaster\"    — uppercase\n❌ \"correct  horse battery staple printer toaster\"   — double space\n❌ \"correct horse battery staple printer the the\"    — repeated adjacent\n","text",[957,1672,1669],{"__ignoreMap":1054},[940,1674,1675,1676,1679,1680,1683,1684,1687,1688,1691,1692,1696],{},"Word count is enforced separately from the character pattern — a phrase must both match the pattern ",[943,1677,1678],{},"and"," meet ",[957,1681,1682],{},"minWords",". Every rule in the table is overridable per vault via ",[957,1685,1686],{},"SecretPolicy",", including a full ",[957,1689,1690],{},"customValidator"," escape hatch for non-space-delimited scripts or organization-specific rules (see ",[966,1693,1695],{"href":1694},"#api-reference","API reference",").",[1573,1698,1700],{"id":1699},"strength-validation","Strength validation",[1049,1702,1704],{"className":1051,"code":1703,"language":1053,"meta":1054,"style":1054},"import { validateSecret, assertStrongSecret, WeakSecretError } from '@noy-db\u002Fhub'\n\nvalidateSecret('correct horse battery staple printer toaster')\n\u002F\u002F → { ok: true, words: 6 }\n\nvalidateSecret('abc')\n\u002F\u002F → { ok: false, reason: 'too-few-words', minimum: 6, got: 1 }\n\nassertStrongSecret('abc')\n\u002F\u002F → throws WeakSecretError { reason: 'too-few-words', suggestion: '...' }\n",[957,1705,1706,1735,1739,1755,1760,1765,1781,1787,1792,1808],{"__ignoreMap":1054},[1058,1707,1708,1710,1712,1715,1717,1720,1722,1725,1727,1729,1731,1733],{"class":1060,"line":1061},[1058,1709,1065],{"class":1064},[1058,1711,1069],{"class":1068},[1058,1713,1714],{"class":1072}," validateSecret",[1058,1716,1133],{"class":1068},[1058,1718,1719],{"class":1072}," assertStrongSecret",[1058,1721,1133],{"class":1068},[1058,1723,1724],{"class":1072}," WeakSecretError",[1058,1726,1076],{"class":1068},[1058,1728,1079],{"class":1064},[1058,1730,1082],{"class":1068},[1058,1732,992],{"class":1085},[1058,1734,1088],{"class":1068},[1058,1736,1737],{"class":1060,"line":1091},[1058,1738,1095],{"emptyLinePlaceholder":1094},[1058,1740,1741,1744,1746,1748,1751,1753],{"class":1060,"line":1098},[1058,1742,1743],{"class":1121},"validateSecret",[1058,1745,1124],{"class":1072},[1058,1747,1229],{"class":1068},[1058,1749,1750],{"class":1085},"correct horse battery staple printer toaster",[1058,1752,1229],{"class":1068},[1058,1754,1142],{"class":1072},[1058,1756,1757],{"class":1060,"line":1105},[1058,1758,1759],{"class":1101},"\u002F\u002F → { ok: true, words: 6 }\n",[1058,1761,1763],{"class":1060,"line":1762},5,[1058,1764,1095],{"emptyLinePlaceholder":1094},[1058,1766,1768,1770,1772,1774,1777,1779],{"class":1060,"line":1767},6,[1058,1769,1743],{"class":1121},[1058,1771,1124],{"class":1072},[1058,1773,1229],{"class":1068},[1058,1775,1776],{"class":1085},"abc",[1058,1778,1229],{"class":1068},[1058,1780,1142],{"class":1072},[1058,1782,1784],{"class":1060,"line":1783},7,[1058,1785,1786],{"class":1101},"\u002F\u002F → { ok: false, reason: 'too-few-words', minimum: 6, got: 1 }\n",[1058,1788,1790],{"class":1060,"line":1789},8,[1058,1791,1095],{"emptyLinePlaceholder":1094},[1058,1793,1795,1798,1800,1802,1804,1806],{"class":1060,"line":1794},9,[1058,1796,1797],{"class":1121},"assertStrongSecret",[1058,1799,1124],{"class":1072},[1058,1801,1229],{"class":1068},[1058,1803,1776],{"class":1085},[1058,1805,1229],{"class":1068},[1058,1807,1142],{"class":1072},[1058,1809,1811],{"class":1060,"line":1810},10,[1058,1812,1813],{"class":1101},"\u002F\u002F → throws WeakSecretError { reason: 'too-few-words', suggestion: '...' }\n",[940,1815,1816],{},"Enforcement differs by ingress point:",[1011,1818,1819,1849],{},[1014,1820,1821,1824,1825,1828,1829,1834,1835,1838,1839,1841,1842,1845,1846,968],{},[943,1822,1823],{},"First-time vault creation"," (",[957,1826,1827],{},"createNoydb",") and ",[943,1830,1831],{},[957,1832,1833],{},"grant()"," validate the phrase only when the caller opts in — pass ",[957,1836,1837],{},"validateSecret: true"," to ",[957,1840,1827],{},", or ",[957,1843,1844],{},"{ validateSecret: true }"," in ",[957,1847,1848],{},"GrantOptions",[1014,1850,1851,1158,1856,1861,1862,1865,1866,1869],{},[943,1852,1853],{},[957,1854,1855],{},"db.rotateSecret",[943,1857,1858],{},[957,1859,1860],{},"db.recoverSecret"," validate the ",[948,1863,1864],{},"new"," phrase by default; pass ",[957,1867,1868],{},"{ allowWeakSecret: true }"," to bypass (test fixtures, CLI scripts).",[1049,1871,1873],{"className":1051,"code":1872,"language":1053,"meta":1054,"style":1054},"const db = await createNoydb({ store, validateSecret: true })\n\u002F\u002F → new vaults now throw WeakSecretError on a weak initial secret\n",[957,1874,1875,1907],{"__ignoreMap":1054},[1058,1876,1877,1879,1881,1883,1885,1887,1889,1891,1893,1895,1897,1899,1903,1905],{"class":1060,"line":1061},[1058,1878,1109],{"class":1108},[1058,1880,1112],{"class":1072},[1058,1882,1115],{"class":1068},[1058,1884,1118],{"class":1064},[1058,1886,1073],{"class":1121},[1058,1888,1124],{"class":1072},[1058,1890,1127],{"class":1068},[1058,1892,1130],{"class":1072},[1058,1894,1133],{"class":1068},[1058,1896,1714],{"class":1218},[1058,1898,1167],{"class":1068},[1058,1900,1902],{"class":1901},"sfNiH"," true",[1058,1904,1076],{"class":1068},[1058,1906,1142],{"class":1072},[1058,1908,1909],{"class":1060,"line":1091},[1058,1910,1911],{"class":1101},"\u002F\u002F → new vaults now throw WeakSecretError on a weak initial secret\n",[1573,1913,1915],{"id":1914},"secret-handling-developer-responsibility","Secret handling (developer responsibility)",[940,1917,1918],{},"The cryptographic risk is in the hub; the handling discipline is in your code:",[1011,1920,1921,1924,1931],{},[1014,1922,1923],{},"Never write the secret to logs, telemetry, error reports, or analytics.",[1014,1925,1926,1927,1930],{},"Never persist it in ",[957,1928,1929],{},"localStorage",", cookies, or any cache that outlives the unlock call.",[1014,1932,1933,1934,1937],{},"Use ",[957,1935,1936],{},"\u003Cinput type=\"password\" autocomplete=\"new-password\">"," for enrollment forms — don't pre-fill from clipboard or autofill.",[1468,1939,1941],{"id":1940},"tier-2-authenticate-multi-slot","Tier 2 — Authenticate (multi-slot)",[940,1943,1944,1945,1948,1949,1951],{},"A user enrolls one or more ",[943,1946,1947],{},"authenticator slots",". Each slot independently wraps the same KEK (or, for ",[957,1950,1386],{},", the DEK set directly) under a method-specific key — any one enrolled slot unlocks the vault. Adding or removing a slot is a constant-time keyring write; it never re-keys the DEKs.",[1049,1953,1955],{"className":1051,"code":1954,"language":1053,"meta":1054,"style":1054},"interface KeyringAuthenticatorBase {\n  id: string                          \u002F\u002F caller-chosen — e.g. 'webauthn-yubikey-blue'\n  method: 'webauthn' | 'oidc' | 'password'\n  enrolled_at: string                 \u002F\u002F ISO 8601\n  enrolled_via_tier: 1 | 2            \u002F\u002F tier that enrolled this slot\n  meta: Record\u003Cstring, unknown>       \u002F\u002F method-specific — cred id, issuer\u002Fsub, salt, ...\n}\n\n\u002F\u002F wrap-KEK slots (webauthn, oidc) — wrap the SAME KEK under a method-derived key\ntype KeyringAuthenticatorWrappingKEK = KeyringAuthenticatorBase & {\n  wrapKind?: 'kek'\n  wrapped_kek: string\n}\n\n\u002F\u002F wrap-DEKs slots (password) — wrap the DEK set directly, sidestepping the non-extractable KEK\ntype KeyringAuthenticatorWrappingDEKs = KeyringAuthenticatorBase & {\n  wrapKind: 'deks'\n  wrapped_deks: string\n  iv: string\n}\n\ntype KeyringAuthenticator = KeyringAuthenticatorWrappingKEK | KeyringAuthenticatorWrappingDEKs\n",[957,1956,1957,1969,1982,2012,2024,2042,2069,2074,2078,2083,2101,2117,2128,2133,2138,2144,2160,2174,2184,2194,2199,2204],{"__ignoreMap":1054},[1058,1958,1959,1962,1966],{"class":1060,"line":1061},[1058,1960,1961],{"class":1108},"interface",[1058,1963,1965],{"class":1964},"sBMFI"," KeyringAuthenticatorBase",[1058,1967,1968],{"class":1068}," {\n",[1058,1970,1971,1974,1976,1979],{"class":1060,"line":1091},[1058,1972,1973],{"class":1218},"  id",[1058,1975,1167],{"class":1068},[1058,1977,1978],{"class":1964}," string",[1058,1980,1981],{"class":1101},"                          \u002F\u002F caller-chosen — e.g. 'webauthn-yubikey-blue'\n",[1058,1983,1984,1987,1989,1991,1993,1995,1998,2000,2002,2004,2006,2008,2010],{"class":1060,"line":1098},[1058,1985,1986],{"class":1218},"  method",[1058,1988,1167],{"class":1068},[1058,1990,1082],{"class":1068},[1058,1992,337],{"class":1085},[1058,1994,1229],{"class":1068},[1058,1996,1997],{"class":1068}," |",[1058,1999,1082],{"class":1068},[1058,2001,309],{"class":1085},[1058,2003,1229],{"class":1068},[1058,2005,1997],{"class":1068},[1058,2007,1082],{"class":1068},[1058,2009,313],{"class":1085},[1058,2011,1088],{"class":1068},[1058,2013,2014,2017,2019,2021],{"class":1060,"line":1105},[1058,2015,2016],{"class":1218},"  enrolled_at",[1058,2018,1167],{"class":1068},[1058,2020,1978],{"class":1964},[1058,2022,2023],{"class":1101},"                 \u002F\u002F ISO 8601\n",[1058,2025,2026,2029,2031,2034,2036,2039],{"class":1060,"line":1762},[1058,2027,2028],{"class":1218},"  enrolled_via_tier",[1058,2030,1167],{"class":1068},[1058,2032,2033],{"class":1239}," 1",[1058,2035,1997],{"class":1068},[1058,2037,2038],{"class":1239}," 2",[1058,2040,2041],{"class":1101},"            \u002F\u002F tier that enrolled this slot\n",[1058,2043,2044,2047,2049,2052,2055,2058,2060,2063,2066],{"class":1060,"line":1767},[1058,2045,2046],{"class":1218},"  meta",[1058,2048,1167],{"class":1068},[1058,2050,2051],{"class":1964}," Record",[1058,2053,2054],{"class":1068},"\u003C",[1058,2056,2057],{"class":1964},"string",[1058,2059,1133],{"class":1068},[1058,2061,2062],{"class":1964}," unknown",[1058,2064,2065],{"class":1068},">",[1058,2067,2068],{"class":1101},"       \u002F\u002F method-specific — cred id, issuer\u002Fsub, salt, ...\n",[1058,2070,2071],{"class":1060,"line":1783},[1058,2072,2073],{"class":1068},"}\n",[1058,2075,2076],{"class":1060,"line":1789},[1058,2077,1095],{"emptyLinePlaceholder":1094},[1058,2079,2080],{"class":1060,"line":1794},[1058,2081,2082],{"class":1101},"\u002F\u002F wrap-KEK slots (webauthn, oidc) — wrap the SAME KEK under a method-derived key\n",[1058,2084,2085,2088,2091,2094,2096,2099],{"class":1060,"line":1810},[1058,2086,2087],{"class":1108},"type",[1058,2089,2090],{"class":1964}," KeyringAuthenticatorWrappingKEK",[1058,2092,2093],{"class":1068}," =",[1058,2095,1965],{"class":1964},[1058,2097,2098],{"class":1068}," &",[1058,2100,1968],{"class":1068},[1058,2102,2104,2107,2110,2112,2115],{"class":1060,"line":2103},11,[1058,2105,2106],{"class":1218},"  wrapKind",[1058,2108,2109],{"class":1068},"?:",[1058,2111,1082],{"class":1068},[1058,2113,2114],{"class":1085},"kek",[1058,2116,1088],{"class":1068},[1058,2118,2120,2123,2125],{"class":1060,"line":2119},12,[1058,2121,2122],{"class":1218},"  wrapped_kek",[1058,2124,1167],{"class":1068},[1058,2126,2127],{"class":1964}," string\n",[1058,2129,2131],{"class":1060,"line":2130},13,[1058,2132,2073],{"class":1068},[1058,2134,2136],{"class":1060,"line":2135},14,[1058,2137,1095],{"emptyLinePlaceholder":1094},[1058,2139,2141],{"class":1060,"line":2140},15,[1058,2142,2143],{"class":1101},"\u002F\u002F wrap-DEKs slots (password) — wrap the DEK set directly, sidestepping the non-extractable KEK\n",[1058,2145,2147,2149,2152,2154,2156,2158],{"class":1060,"line":2146},16,[1058,2148,2087],{"class":1108},[1058,2150,2151],{"class":1964}," KeyringAuthenticatorWrappingDEKs",[1058,2153,2093],{"class":1068},[1058,2155,1965],{"class":1964},[1058,2157,2098],{"class":1068},[1058,2159,1968],{"class":1068},[1058,2161,2163,2165,2167,2169,2172],{"class":1060,"line":2162},17,[1058,2164,2106],{"class":1218},[1058,2166,1167],{"class":1068},[1058,2168,1082],{"class":1068},[1058,2170,2171],{"class":1085},"deks",[1058,2173,1088],{"class":1068},[1058,2175,2177,2180,2182],{"class":1060,"line":2176},18,[1058,2178,2179],{"class":1218},"  wrapped_deks",[1058,2181,1167],{"class":1068},[1058,2183,2127],{"class":1964},[1058,2185,2187,2190,2192],{"class":1060,"line":2186},19,[1058,2188,2189],{"class":1218},"  iv",[1058,2191,1167],{"class":1068},[1058,2193,2127],{"class":1964},[1058,2195,2197],{"class":1060,"line":2196},20,[1058,2198,2073],{"class":1068},[1058,2200,2202],{"class":1060,"line":2201},21,[1058,2203,1095],{"emptyLinePlaceholder":1094},[1058,2205,2207,2209,2212,2214,2216,2218],{"class":1060,"line":2206},22,[1058,2208,2087],{"class":1108},[1058,2210,2211],{"class":1964}," KeyringAuthenticator",[1058,2213,2093],{"class":1068},[1058,2215,2090],{"class":1964},[1058,2217,1997],{"class":1068},[1058,2219,2220],{"class":1964}," KeyringAuthenticatorWrappingDEKs\n",[1573,2222,2224],{"id":2223},"available-tier-2-methods","Available tier-2 methods",[1292,2226,2227,2243],{},[1295,2228,2229],{},[1298,2230,2231,2234,2237,2240],{},[1301,2232,2233],{},"Method",[1301,2235,2236],{},"Package",[1301,2238,2239],{},"Factor",[1301,2241,2242],{},"Notes",[1317,2244,2245,2266,2283],{},[1298,2246,2247,2251,2256,2259],{},[1322,2248,2249],{},[957,2250,337],{},[1322,2252,2253],{},[957,2254,2255],{},"@noy-db\u002Fon-webauthn",[1322,2257,2258],{},"device (platform passkey) or possession (roaming key)",[1322,2260,2261,2262,2265],{},"Prefers the PRF extension for deterministic key derivation; falls back to a rawId-derived key. ",[957,2263,2264],{},"requireSingleDevice"," rejects credentials whose backup-eligible (BE) flag is set.",[1298,2267,2268,2272,2277,2280],{},[1322,2269,2270],{},[957,2271,309],{},[1322,2273,2274],{},[957,2275,2276],{},"@noy-db\u002Fon-oidc",[1322,2278,2279],{},"possession (provider session) + knowledge (split-key secret half)",[1322,2281,2282],{},"Bitwarden-style split key: half the KEK lives on the device, half comes from the OIDC provider's key connector on successful login.",[1298,2284,2285,2289,2294,2297],{},[1322,2286,2287],{},[957,2288,313],{},[1322,2290,2291],{},[957,2292,2293],{},"@noy-db\u002Fon-password",[1322,2295,2296],{},"knowledge (a second secret, distinct from the tier-1 phrase)",[1322,2298,2299,2300,2302,2303,2305],{},"PBKDF2-derived wrap-DEKs slot. A separate credential from the tier-1 phrase — pair with ",[957,2301,1436],{}," or ",[957,2304,1440],{}," for a typical SaaS UX.",[1573,2307,2309],{"id":2308},"enroll-remove-update-unlock","Enroll, remove, update, unlock",[940,2311,2312,1380,2315,1380,2318,2321,2322,2325],{},[957,2313,2314],{},"enrollAuthenticator",[957,2316,2317],{},"removeAuthenticator",[957,2319,2320],{},"updateAuthenticator"," all take the ",[943,2323,2324],{},"vault name",", not a keyring — the hub checks the relevant policy gate and fetches the active keyring internally.",[1049,2327,2329],{"className":1051,"code":2328,"language":1053,"meta":1054,"style":1054},"\u002F\u002F Enroll a new slot. Checks the `enroll-authenticator` gate, then persists.\nawait db.team.enrollAuthenticator('acme', {\n  id: 'webauthn-yubikey-blue',\n  method: 'webauthn',\n  wrapped_kek: enrollment.wrappedKek,        \u002F\u002F produced by @noy-db\u002Fon-webauthn\n  meta: { credentialId: enrollment.credentialId },\n})\n\n\u002F\u002F Remove a slot. Idempotent — removing a missing slot is a no-op. Gated by `remove-authenticator`.\nawait db.team.removeAuthenticator('acme', 'webauthn-yubikey-blue')\n\n\u002F\u002F Rename or re-label a slot's meta (id\u002Fmethod\u002Fwrap material are immutable). Gated by `update-authenticator`.\nawait db.team.updateAuthenticator('acme', 'webauthn-yubikey-blue', { meta: { label: 'Work laptop' } })\n\n\u002F\u002F Unlock via an enrolled slot — `verify` receives the stored slot and\n\u002F\u002F returns the unlocked keyring (typically delegating to the matching on-* package).\nconst unlocked = await db.team.unlockViaAuthenticator('acme', 'webauthn-yubikey-blue', async (slot) => {\n  return unlockWebAuthn(slot, \u002F* on-webauthn assertion options *\u002F)\n})\n\u002F\u002F → unlocked keyring; subsequent checkGate() calls now see a tier-2 session\n",[957,2330,2331,2336,2362,2378,2392,2411,2434,2440,2444,2449,2481,2485,2490,2549,2553,2558,2563,2620,2639,2645],{"__ignoreMap":1054},[1058,2332,2333],{"class":1060,"line":1061},[1058,2334,2335],{"class":1101},"\u002F\u002F Enroll a new slot. Checks the `enroll-authenticator` gate, then persists.\n",[1058,2337,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360],{"class":1060,"line":1091},[1058,2339,1260],{"class":1064},[1058,2341,1263],{"class":1072},[1058,2343,968],{"class":1068},[1058,2345,868],{"class":1072},[1058,2347,968],{"class":1068},[1058,2349,2314],{"class":1121},[1058,2351,1124],{"class":1072},[1058,2353,1229],{"class":1068},[1058,2355,1279],{"class":1085},[1058,2357,1229],{"class":1068},[1058,2359,1133],{"class":1068},[1058,2361,1968],{"class":1068},[1058,2363,2364,2366,2368,2370,2373,2375],{"class":1060,"line":1098},[1058,2365,1973],{"class":1218},[1058,2367,1167],{"class":1068},[1058,2369,1082],{"class":1068},[1058,2371,2372],{"class":1085},"webauthn-yubikey-blue",[1058,2374,1229],{"class":1068},[1058,2376,2377],{"class":1068},",\n",[1058,2379,2380,2382,2384,2386,2388,2390],{"class":1060,"line":1105},[1058,2381,1986],{"class":1218},[1058,2383,1167],{"class":1068},[1058,2385,1082],{"class":1068},[1058,2387,337],{"class":1085},[1058,2389,1229],{"class":1068},[1058,2391,2377],{"class":1068},[1058,2393,2394,2396,2398,2401,2403,2406,2408],{"class":1060,"line":1762},[1058,2395,2122],{"class":1218},[1058,2397,1167],{"class":1068},[1058,2399,2400],{"class":1072}," enrollment",[1058,2402,968],{"class":1068},[1058,2404,2405],{"class":1072},"wrappedKek",[1058,2407,1133],{"class":1068},[1058,2409,2410],{"class":1101},"        \u002F\u002F produced by @noy-db\u002Fon-webauthn\n",[1058,2412,2413,2415,2417,2419,2422,2424,2426,2428,2431],{"class":1060,"line":1767},[1058,2414,2046],{"class":1218},[1058,2416,1167],{"class":1068},[1058,2418,1069],{"class":1068},[1058,2420,2421],{"class":1218}," credentialId",[1058,2423,1167],{"class":1068},[1058,2425,2400],{"class":1072},[1058,2427,968],{"class":1068},[1058,2429,2430],{"class":1072},"credentialId ",[1058,2432,2433],{"class":1068},"},\n",[1058,2435,2436,2438],{"class":1060,"line":1783},[1058,2437,1139],{"class":1068},[1058,2439,1142],{"class":1072},[1058,2441,2442],{"class":1060,"line":1789},[1058,2443,1095],{"emptyLinePlaceholder":1094},[1058,2445,2446],{"class":1060,"line":1794},[1058,2447,2448],{"class":1101},"\u002F\u002F Remove a slot. Idempotent — removing a missing slot is a no-op. Gated by `remove-authenticator`.\n",[1058,2450,2451,2453,2455,2457,2459,2461,2463,2465,2467,2469,2471,2473,2475,2477,2479],{"class":1060,"line":1810},[1058,2452,1260],{"class":1064},[1058,2454,1263],{"class":1072},[1058,2456,968],{"class":1068},[1058,2458,868],{"class":1072},[1058,2460,968],{"class":1068},[1058,2462,2317],{"class":1121},[1058,2464,1124],{"class":1072},[1058,2466,1229],{"class":1068},[1058,2468,1279],{"class":1085},[1058,2470,1229],{"class":1068},[1058,2472,1133],{"class":1068},[1058,2474,1082],{"class":1068},[1058,2476,2372],{"class":1085},[1058,2478,1229],{"class":1068},[1058,2480,1142],{"class":1072},[1058,2482,2483],{"class":1060,"line":2103},[1058,2484,1095],{"emptyLinePlaceholder":1094},[1058,2486,2487],{"class":1060,"line":2119},[1058,2488,2489],{"class":1101},"\u002F\u002F Rename or re-label a slot's meta (id\u002Fmethod\u002Fwrap material are immutable). Gated by `update-authenticator`.\n",[1058,2491,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2527,2529,2531,2534,2536,2538,2541,2543,2545,2547],{"class":1060,"line":2130},[1058,2493,1260],{"class":1064},[1058,2495,1263],{"class":1072},[1058,2497,968],{"class":1068},[1058,2499,868],{"class":1072},[1058,2501,968],{"class":1068},[1058,2503,2320],{"class":1121},[1058,2505,1124],{"class":1072},[1058,2507,1229],{"class":1068},[1058,2509,1279],{"class":1085},[1058,2511,1229],{"class":1068},[1058,2513,1133],{"class":1068},[1058,2515,1082],{"class":1068},[1058,2517,2372],{"class":1085},[1058,2519,1229],{"class":1068},[1058,2521,1133],{"class":1068},[1058,2523,1069],{"class":1068},[1058,2525,2526],{"class":1218}," meta",[1058,2528,1167],{"class":1068},[1058,2530,1069],{"class":1068},[1058,2532,2533],{"class":1218}," label",[1058,2535,1167],{"class":1068},[1058,2537,1082],{"class":1068},[1058,2539,2540],{"class":1085},"Work laptop",[1058,2542,1229],{"class":1068},[1058,2544,1076],{"class":1068},[1058,2546,1076],{"class":1068},[1058,2548,1142],{"class":1072},[1058,2550,2551],{"class":1060,"line":2135},[1058,2552,1095],{"emptyLinePlaceholder":1094},[1058,2554,2555],{"class":1060,"line":2140},[1058,2556,2557],{"class":1101},"\u002F\u002F Unlock via an enrolled slot — `verify` receives the stored slot and\n",[1058,2559,2560],{"class":1060,"line":2146},[1058,2561,2562],{"class":1101},"\u002F\u002F returns the unlocked keyring (typically delegating to the matching on-* package).\n",[1058,2564,2565,2567,2570,2572,2574,2576,2578,2580,2582,2585,2587,2589,2591,2593,2595,2597,2599,2601,2603,2606,2608,2612,2615,2618],{"class":1060,"line":2162},[1058,2566,1109],{"class":1108},[1058,2568,2569],{"class":1072}," unlocked ",[1058,2571,1115],{"class":1068},[1058,2573,1118],{"class":1064},[1058,2575,1263],{"class":1072},[1058,2577,968],{"class":1068},[1058,2579,868],{"class":1072},[1058,2581,968],{"class":1068},[1058,2583,2584],{"class":1121},"unlockViaAuthenticator",[1058,2586,1124],{"class":1072},[1058,2588,1229],{"class":1068},[1058,2590,1279],{"class":1085},[1058,2592,1229],{"class":1068},[1058,2594,1133],{"class":1068},[1058,2596,1082],{"class":1068},[1058,2598,2372],{"class":1085},[1058,2600,1229],{"class":1068},[1058,2602,1133],{"class":1068},[1058,2604,2605],{"class":1108}," async",[1058,2607,1824],{"class":1068},[1058,2609,2611],{"class":2610},"sHdIc","slot",[1058,2613,2614],{"class":1068},")",[1058,2616,2617],{"class":1108}," =>",[1058,2619,1968],{"class":1068},[1058,2621,2622,2625,2628,2630,2632,2634,2637],{"class":1060,"line":2176},[1058,2623,2624],{"class":1064},"  return",[1058,2626,2627],{"class":1121}," unlockWebAuthn",[1058,2629,1124],{"class":1218},[1058,2631,2611],{"class":1072},[1058,2633,1133],{"class":1068},[1058,2635,2636],{"class":1101}," \u002F* on-webauthn assertion options *\u002F",[1058,2638,1142],{"class":1218},[1058,2640,2641,2643],{"class":1060,"line":2186},[1058,2642,1139],{"class":1068},[1058,2644,1142],{"class":1072},[1058,2646,2647],{"class":1060,"line":2196},[1058,2648,2649],{"class":1101},"\u002F\u002F → unlocked keyring; subsequent checkGate() calls now see a tier-2 session\n",[1468,2651,2653],{"id":2652},"tier-3-unlock-quick-resume","Tier 3 — Unlock (quick-resume)",[940,2655,2656,2657,2659,2660,2663],{},"The canonical tier-3 primitive is ",[957,2658,1166],{},". Its own words: ",[948,2661,2662],{},"\"PIN never replaces the secret; only resumes an already-unlocked session.\""," A PIN derives a short-lived wrapping key (PBKDF2, 100,000 iterations) over the DEKs already cached from a tier-1 or tier-2 unlock; it cannot open a cold vault. On idle expiry, the cache is wiped and tier 2 must be re-presented.",[1049,2665,2667],{"className":1051,"code":2666,"language":1053,"meta":1054,"style":1054},"import { enrollPin, resumePin } from '@noy-db\u002Fon-pin'\n\n\u002F\u002F After a full unlock — mint a resumable state, keep it in memory only.\nconst pinState = await enrollPin(unlockedKeyring, { pin: '1234', ttlMs: 15 * 60 * 1000 })\nawait db.team.enrollUnlock('acme', pinState)\n\n\u002F\u002F Later, on session resume:\nconst resumed = await db.team.unlockViaPin('acme', (state) => resumePin(state, { pin: '1234' }))\n\u002F\u002F → resumed is `undefined` if no tier-3 state is registered for the vault;\n\u002F\u002F   resumePin() throws PinExpiredError \u002F PinAttemptsExceededError past the TTL\u002Fattempt limit\n",[957,2668,2669,2692,2696,2701,2749,2775,2779,2784,2849,2854],{"__ignoreMap":1054},[1058,2670,2671,2673,2675,2677,2679,2682,2684,2686,2688,2690],{"class":1060,"line":1061},[1058,2672,1065],{"class":1064},[1058,2674,1069],{"class":1068},[1058,2676,1181],{"class":1072},[1058,2678,1133],{"class":1068},[1058,2680,2681],{"class":1072}," resumePin",[1058,2683,1076],{"class":1068},[1058,2685,1079],{"class":1064},[1058,2687,1082],{"class":1068},[1058,2689,1166],{"class":1085},[1058,2691,1088],{"class":1068},[1058,2693,2694],{"class":1060,"line":1091},[1058,2695,1095],{"emptyLinePlaceholder":1094},[1058,2697,2698],{"class":1060,"line":1098},[1058,2699,2700],{"class":1101},"\u002F\u002F After a full unlock — mint a resumable state, keep it in memory only.\n",[1058,2702,2703,2705,2707,2709,2711,2713,2715,2717,2719,2721,2723,2725,2727,2729,2731,2733,2735,2737,2739,2741,2743,2745,2747],{"class":1060,"line":1105},[1058,2704,1109],{"class":1108},[1058,2706,1202],{"class":1072},[1058,2708,1115],{"class":1068},[1058,2710,1118],{"class":1064},[1058,2712,1181],{"class":1121},[1058,2714,1211],{"class":1072},[1058,2716,1133],{"class":1068},[1058,2718,1069],{"class":1068},[1058,2720,1219],{"class":1218},[1058,2722,1167],{"class":1068},[1058,2724,1082],{"class":1068},[1058,2726,1226],{"class":1085},[1058,2728,1229],{"class":1068},[1058,2730,1133],{"class":1068},[1058,2732,1234],{"class":1218},[1058,2734,1167],{"class":1068},[1058,2736,1240],{"class":1239},[1058,2738,1243],{"class":1068},[1058,2740,1246],{"class":1239},[1058,2742,1243],{"class":1068},[1058,2744,1251],{"class":1239},[1058,2746,1076],{"class":1068},[1058,2748,1142],{"class":1072},[1058,2750,2751,2753,2755,2757,2759,2761,2763,2765,2767,2769,2771,2773],{"class":1060,"line":1762},[1058,2752,1260],{"class":1064},[1058,2754,1263],{"class":1072},[1058,2756,968],{"class":1068},[1058,2758,868],{"class":1072},[1058,2760,968],{"class":1068},[1058,2762,1272],{"class":1121},[1058,2764,1124],{"class":1072},[1058,2766,1229],{"class":1068},[1058,2768,1279],{"class":1085},[1058,2770,1229],{"class":1068},[1058,2772,1133],{"class":1068},[1058,2774,1286],{"class":1072},[1058,2776,2777],{"class":1060,"line":1767},[1058,2778,1095],{"emptyLinePlaceholder":1094},[1058,2780,2781],{"class":1060,"line":1783},[1058,2782,2783],{"class":1101},"\u002F\u002F Later, on session resume:\n",[1058,2785,2786,2788,2791,2793,2795,2797,2799,2801,2803,2806,2808,2810,2812,2814,2816,2818,2821,2823,2825,2827,2830,2832,2834,2836,2838,2840,2842,2844,2846],{"class":1060,"line":1789},[1058,2787,1109],{"class":1108},[1058,2789,2790],{"class":1072}," resumed ",[1058,2792,1115],{"class":1068},[1058,2794,1118],{"class":1064},[1058,2796,1263],{"class":1072},[1058,2798,968],{"class":1068},[1058,2800,868],{"class":1072},[1058,2802,968],{"class":1068},[1058,2804,2805],{"class":1121},"unlockViaPin",[1058,2807,1124],{"class":1072},[1058,2809,1229],{"class":1068},[1058,2811,1279],{"class":1085},[1058,2813,1229],{"class":1068},[1058,2815,1133],{"class":1068},[1058,2817,1824],{"class":1068},[1058,2819,2820],{"class":2610},"state",[1058,2822,2614],{"class":1068},[1058,2824,2617],{"class":1108},[1058,2826,2681],{"class":1121},[1058,2828,2829],{"class":1072},"(state",[1058,2831,1133],{"class":1068},[1058,2833,1069],{"class":1068},[1058,2835,1219],{"class":1218},[1058,2837,1167],{"class":1068},[1058,2839,1082],{"class":1068},[1058,2841,1226],{"class":1085},[1058,2843,1229],{"class":1068},[1058,2845,1076],{"class":1068},[1058,2847,2848],{"class":1072},"))\n",[1058,2850,2851],{"class":1060,"line":1794},[1058,2852,2853],{"class":1101},"\u002F\u002F → resumed is `undefined` if no tier-3 state is registered for the vault;\n",[1058,2855,2856],{"class":1060,"line":1810},[1058,2857,2858],{"class":1101},"\u002F\u002F   resumePin() throws PinExpiredError \u002F PinAttemptsExceededError past the TTL\u002Fattempt limit\n",[940,2860,2861,2862,2865],{},"Tier 3 is never safe on a shared workstation — the cached DEKs live on the device for the whole idle window. The idle timeout (",[957,2863,2864],{},"ttlMs",") is an application choice: a shared kiosk might use minutes, a single-user tablet longer.",[1468,2867,2869],{"id":2868},"policy-gates","Policy gates",[940,2871,2872,2873,2876,2877,2880,2881,2883,2884,2887],{},"Sensitive operations are gated by a typed ",[957,2874,2875],{},"VaultPolicy",". You supply one at vault creation (or accept the default); the hub persists it once at ",[957,2878,2879],{},"_meta\u002Fpolicy"," and every later ",[957,2882,1038],{}," call for that vault reads the on-disk copy — ",[957,2885,2886],{},"db.updatePolicy()"," is the sanctioned way to change it afterward.",[1049,2889,2891],{"className":1051,"code":2890,"language":1053,"meta":1054,"style":1054},"interface VaultPolicy {\n  secret?: SecretPolicy\n  gates: Partial\u003CRecord\u003CGateName, GatePolicy>>\n}\n\ninterface GatePolicy {\n  minTier: 1 | 2 | 3                            \u002F\u002F session tier required\n  factors?: ReadonlyArray\u003CFactorRequirement>    \u002F\u002F extra fresh proofs at action time\n  warn?: WarningRules                           \u002F\u002F soft signals, never block on their own\n  enabled?: boolean                             \u002F\u002F false disables the action entirely\n}\n\ninterface FactorRequirement {\n  anyOf: ReadonlyArray\u003CFactorKind>              \u002F\u002F 'totp' | 'email-otp' | 'recovery' | 'shamir' |\n                                                 \u002F\u002F 'webauthn-roaming' | 'webauthn-platform' | 'password' | 'pin'\n  count?: number                                \u002F\u002F distinct factors required — default 1\n  freshnessMs?: number                          \u002F\u002F default 5 minutes\n}\n",[957,2892,2893,2902,2912,2940,2944,2948,2956,2977,2997,3010,3023,3027,3031,3040,3059,3064,3077,3089],{"__ignoreMap":1054},[1058,2894,2895,2897,2900],{"class":1060,"line":1061},[1058,2896,1961],{"class":1108},[1058,2898,2899],{"class":1964}," VaultPolicy",[1058,2901,1968],{"class":1068},[1058,2903,2904,2907,2909],{"class":1060,"line":1091},[1058,2905,2906],{"class":1218},"  secret",[1058,2908,2109],{"class":1068},[1058,2910,2911],{"class":1964}," SecretPolicy\n",[1058,2913,2914,2917,2919,2922,2924,2927,2929,2932,2934,2937],{"class":1060,"line":1098},[1058,2915,2916],{"class":1218},"  gates",[1058,2918,1167],{"class":1068},[1058,2920,2921],{"class":1964}," Partial",[1058,2923,2054],{"class":1068},[1058,2925,2926],{"class":1964},"Record",[1058,2928,2054],{"class":1068},[1058,2930,2931],{"class":1964},"GateName",[1058,2933,1133],{"class":1068},[1058,2935,2936],{"class":1964}," GatePolicy",[1058,2938,2939],{"class":1068},">>\n",[1058,2941,2942],{"class":1060,"line":1105},[1058,2943,2073],{"class":1068},[1058,2945,2946],{"class":1060,"line":1762},[1058,2947,1095],{"emptyLinePlaceholder":1094},[1058,2949,2950,2952,2954],{"class":1060,"line":1767},[1058,2951,1961],{"class":1108},[1058,2953,2936],{"class":1964},[1058,2955,1968],{"class":1068},[1058,2957,2958,2961,2963,2965,2967,2969,2971,2974],{"class":1060,"line":1783},[1058,2959,2960],{"class":1218},"  minTier",[1058,2962,1167],{"class":1068},[1058,2964,2033],{"class":1239},[1058,2966,1997],{"class":1068},[1058,2968,2038],{"class":1239},[1058,2970,1997],{"class":1068},[1058,2972,2973],{"class":1239}," 3",[1058,2975,2976],{"class":1101},"                            \u002F\u002F session tier required\n",[1058,2978,2979,2982,2984,2987,2989,2992,2994],{"class":1060,"line":1789},[1058,2980,2981],{"class":1218},"  factors",[1058,2983,2109],{"class":1068},[1058,2985,2986],{"class":1964}," ReadonlyArray",[1058,2988,2054],{"class":1068},[1058,2990,2991],{"class":1964},"FactorRequirement",[1058,2993,2065],{"class":1068},[1058,2995,2996],{"class":1101},"    \u002F\u002F extra fresh proofs at action time\n",[1058,2998,2999,3002,3004,3007],{"class":1060,"line":1794},[1058,3000,3001],{"class":1218},"  warn",[1058,3003,2109],{"class":1068},[1058,3005,3006],{"class":1964}," WarningRules",[1058,3008,3009],{"class":1101},"                           \u002F\u002F soft signals, never block on their own\n",[1058,3011,3012,3015,3017,3020],{"class":1060,"line":1810},[1058,3013,3014],{"class":1218},"  enabled",[1058,3016,2109],{"class":1068},[1058,3018,3019],{"class":1964}," boolean",[1058,3021,3022],{"class":1101},"                             \u002F\u002F false disables the action entirely\n",[1058,3024,3025],{"class":1060,"line":2103},[1058,3026,2073],{"class":1068},[1058,3028,3029],{"class":1060,"line":2119},[1058,3030,1095],{"emptyLinePlaceholder":1094},[1058,3032,3033,3035,3038],{"class":1060,"line":2130},[1058,3034,1961],{"class":1108},[1058,3036,3037],{"class":1964}," FactorRequirement",[1058,3039,1968],{"class":1068},[1058,3041,3042,3045,3047,3049,3051,3054,3056],{"class":1060,"line":2135},[1058,3043,3044],{"class":1218},"  anyOf",[1058,3046,1167],{"class":1068},[1058,3048,2986],{"class":1964},[1058,3050,2054],{"class":1068},[1058,3052,3053],{"class":1964},"FactorKind",[1058,3055,2065],{"class":1068},[1058,3057,3058],{"class":1101},"              \u002F\u002F 'totp' | 'email-otp' | 'recovery' | 'shamir' |\n",[1058,3060,3061],{"class":1060,"line":2140},[1058,3062,3063],{"class":1101},"                                                 \u002F\u002F 'webauthn-roaming' | 'webauthn-platform' | 'password' | 'pin'\n",[1058,3065,3066,3069,3071,3074],{"class":1060,"line":2146},[1058,3067,3068],{"class":1218},"  count",[1058,3070,2109],{"class":1068},[1058,3072,3073],{"class":1964}," number",[1058,3075,3076],{"class":1101},"                                \u002F\u002F distinct factors required — default 1\n",[1058,3078,3079,3082,3084,3086],{"class":1060,"line":2162},[1058,3080,3081],{"class":1218},"  freshnessMs",[1058,3083,2109],{"class":1068},[1058,3085,3073],{"class":1964},[1058,3087,3088],{"class":1101},"                          \u002F\u002F default 5 minutes\n",[1058,3090,3091],{"class":1060,"line":2176},[1058,3092,2073],{"class":1068},[940,3094,3095,3096,3099,3100,3103,3104,3107],{},"App-defined gates use the ",[957,3097,3098],{},"app:\u003Cname>"," namespace (e.g. ",[957,3101,3102],{},"app:approve-large-payment",") and run through the same engine; an ",[957,3105,3106],{},"app:*"," gate with no configured policy is treated as an unenforced label rather than failing closed.",[1573,3109,3111],{"id":3110},"built-in-gates-session-tier-subset","Built-in gates (session-tier subset)",[940,3113,3114],{},"Selected built-in gates and their defaults under the two shipped presets — the engine also enforces built-in gates for team\u002Fwithdrawal\u002Fcustody operations outside the scope of this page:",[1292,3116,3117,3136],{},[1295,3118,3119],{},[1298,3120,3121,3124,3127,3131],{},[1301,3122,3123],{},"Gate",[1301,3125,3126],{},"Purpose",[1301,3128,3129,1514],{},[957,3130,1042],{},[1301,3132,3133],{},[957,3134,3135],{},"STRICT_POLICY",[1317,3137,3138,3159,3176,3195,3213,3232,3253,3276],{},[1298,3139,3140,3145,3148,3154],{},[1322,3141,3142],{},[957,3143,3144],{},"rotate-secret",[1322,3146,3147],{},"User remembers old, sets new",[1322,3149,3150,3153],{},[957,3151,3152],{},"minTier: 1",", any one of totp \u002F email-otp \u002F recovery \u002F webauthn \u002F password \u002F pin",[1322,3155,3156,3158],{},[957,3157,3152],{},", 2 distinct of totp \u002F email-otp \u002F recovery",[1298,3160,3161,3166,3169,3174],{},[1322,3162,3163],{},[957,3164,3165],{},"recover-secret",[1322,3167,3168],{},"User forgot, supplies a recovery proof",[1322,3170,3171,3173],{},[957,3172,3152],{},", enabled",[1322,3175,1614],{},[1298,3177,3178,3183,3186,3190],{},[1322,3179,3180],{},[957,3181,3182],{},"enroll-authenticator",[1322,3184,3185],{},"Add a tier-2 slot",[1322,3187,3188],{},[957,3189,3152],{},[1322,3191,3192,3194],{},[957,3193,3152],{}," + totp\u002Femail-otp",[1298,3196,3197,3202,3205,3209],{},[1322,3198,3199],{},[957,3200,3201],{},"remove-authenticator",[1322,3203,3204],{},"Remove a tier-2 slot",[1322,3206,3207],{},[957,3208,3152],{},[1322,3210,3211,3194],{},[957,3212,3152],{},[1298,3214,3215,3220,3223,3228],{},[1322,3216,3217],{},[957,3218,3219],{},"rotate-unlock",[1322,3221,3222],{},"Change the tier-3 method",[1322,3224,3225],{},[957,3226,3227],{},"minTier: 2",[1322,3229,3230],{},[957,3231,3152],{},[1298,3233,3234,3242,3245,3249],{},[1322,3235,3236,1380,3239],{},[957,3237,3238],{},"enroll-user",[957,3240,3241],{},"revoke-user",[1322,3243,3244],{},"Grant or revoke a keyring",[1322,3246,3247],{},[957,3248,3152],{},[1322,3250,3251,3194],{},[957,3252,3152],{},[1298,3254,3255,3260,3267,3271],{},[1322,3256,3257],{},[957,3258,3259],{},"export-bundle",[1322,3261,3262,3263,3266],{},"Encrypted ",[957,3264,3265],{},".noydb"," bundle export",[1322,3268,3269],{},[957,3270,3152],{},[1322,3272,3273,3275],{},[957,3274,3152],{}," + totp\u002Femail-otp, blocks on a shared device",[1298,3277,3278,3283,3286,3290],{},[1322,3279,3280],{},[957,3281,3282],{},"export-plaintext",[1322,3284,3285],{},"CSV\u002FXLSX\u002FJSON export",[1322,3287,3288,3194],{},[957,3289,3152],{},[1322,3291,3292,3294],{},[957,3293,3152],{},", 2 distinct of totp\u002Femail-otp, blocks on a shared device",[940,3296,3297],{},"Unspecified gates inherit from the preset; overriding one gate never wipes out the rest.",[1573,3299,3301],{"id":3300},"presets-and-overrides","Presets and overrides",[1049,3303,3305],{"className":1051,"code":3304,"language":1053,"meta":1054,"style":1054},"import { createNoydb, PERSONAL_POLICY, STRICT_POLICY } from '@noy-db\u002Fhub'\n\n\u002F\u002F Default — single-user \u002F SMB\nconst db = await createNoydb({ store, policy: PERSONAL_POLICY })\n\n\u002F\u002F Strict — regulated \u002F shared-workstation\nconst db = await createNoydb({ store, policy: STRICT_POLICY })\n\n\u002F\u002F Custom override on top of a preset — unspecified gates keep the preset's values\nconst db = await createNoydb({\n  store,\n  policy: {\n    ...PERSONAL_POLICY,\n    gates: {\n      ...PERSONAL_POLICY.gates,\n      'app:approve-large-payment': { minTier: 2, factors: [{ anyOf: ['totp'] }] },\n    },\n  },\n})\n",[957,3306,3307,3335,3339,3344,3376,3380,3385,3416,3420,3425,3442,3449,3458,3467,3476,3490,3544,3549,3554],{"__ignoreMap":1054},[1058,3308,3309,3311,3313,3315,3317,3320,3322,3325,3327,3329,3331,3333],{"class":1060,"line":1061},[1058,3310,1065],{"class":1064},[1058,3312,1069],{"class":1068},[1058,3314,1073],{"class":1072},[1058,3316,1133],{"class":1068},[1058,3318,3319],{"class":1072}," PERSONAL_POLICY",[1058,3321,1133],{"class":1068},[1058,3323,3324],{"class":1072}," STRICT_POLICY",[1058,3326,1076],{"class":1068},[1058,3328,1079],{"class":1064},[1058,3330,1082],{"class":1068},[1058,3332,992],{"class":1085},[1058,3334,1088],{"class":1068},[1058,3336,3337],{"class":1060,"line":1091},[1058,3338,1095],{"emptyLinePlaceholder":1094},[1058,3340,3341],{"class":1060,"line":1098},[1058,3342,3343],{"class":1101},"\u002F\u002F Default — single-user \u002F SMB\n",[1058,3345,3346,3348,3350,3352,3354,3356,3358,3360,3362,3364,3367,3369,3372,3374],{"class":1060,"line":1105},[1058,3347,1109],{"class":1108},[1058,3349,1112],{"class":1072},[1058,3351,1115],{"class":1068},[1058,3353,1118],{"class":1064},[1058,3355,1073],{"class":1121},[1058,3357,1124],{"class":1072},[1058,3359,1127],{"class":1068},[1058,3361,1130],{"class":1072},[1058,3363,1133],{"class":1068},[1058,3365,3366],{"class":1218}," policy",[1058,3368,1167],{"class":1068},[1058,3370,3371],{"class":1072}," PERSONAL_POLICY ",[1058,3373,1139],{"class":1068},[1058,3375,1142],{"class":1072},[1058,3377,3378],{"class":1060,"line":1762},[1058,3379,1095],{"emptyLinePlaceholder":1094},[1058,3381,3382],{"class":1060,"line":1767},[1058,3383,3384],{"class":1101},"\u002F\u002F Strict — regulated \u002F shared-workstation\n",[1058,3386,3387,3389,3391,3393,3395,3397,3399,3401,3403,3405,3407,3409,3412,3414],{"class":1060,"line":1783},[1058,3388,1109],{"class":1108},[1058,3390,1112],{"class":1072},[1058,3392,1115],{"class":1068},[1058,3394,1118],{"class":1064},[1058,3396,1073],{"class":1121},[1058,3398,1124],{"class":1072},[1058,3400,1127],{"class":1068},[1058,3402,1130],{"class":1072},[1058,3404,1133],{"class":1068},[1058,3406,3366],{"class":1218},[1058,3408,1167],{"class":1068},[1058,3410,3411],{"class":1072}," STRICT_POLICY ",[1058,3413,1139],{"class":1068},[1058,3415,1142],{"class":1072},[1058,3417,3418],{"class":1060,"line":1789},[1058,3419,1095],{"emptyLinePlaceholder":1094},[1058,3421,3422],{"class":1060,"line":1794},[1058,3423,3424],{"class":1101},"\u002F\u002F Custom override on top of a preset — unspecified gates keep the preset's values\n",[1058,3426,3427,3429,3431,3433,3435,3437,3439],{"class":1060,"line":1810},[1058,3428,1109],{"class":1108},[1058,3430,1112],{"class":1072},[1058,3432,1115],{"class":1068},[1058,3434,1118],{"class":1064},[1058,3436,1073],{"class":1121},[1058,3438,1124],{"class":1072},[1058,3440,3441],{"class":1068},"{\n",[1058,3443,3444,3447],{"class":1060,"line":2103},[1058,3445,3446],{"class":1072},"  store",[1058,3448,2377],{"class":1068},[1058,3450,3451,3454,3456],{"class":1060,"line":2119},[1058,3452,3453],{"class":1218},"  policy",[1058,3455,1167],{"class":1068},[1058,3457,1968],{"class":1068},[1058,3459,3460,3463,3465],{"class":1060,"line":2130},[1058,3461,3462],{"class":1068},"    ...",[1058,3464,1042],{"class":1072},[1058,3466,2377],{"class":1068},[1058,3468,3469,3472,3474],{"class":1060,"line":2135},[1058,3470,3471],{"class":1218},"    gates",[1058,3473,1167],{"class":1068},[1058,3475,1968],{"class":1068},[1058,3477,3478,3481,3483,3485,3488],{"class":1060,"line":2140},[1058,3479,3480],{"class":1068},"      ...",[1058,3482,1042],{"class":1072},[1058,3484,968],{"class":1068},[1058,3486,3487],{"class":1072},"gates",[1058,3489,2377],{"class":1068},[1058,3491,3492,3495,3497,3499,3501,3503,3506,3508,3510,3512,3515,3517,3520,3522,3525,3527,3529,3531,3533,3535,3538,3540,3542],{"class":1060,"line":2146},[1058,3493,3494],{"class":1068},"      '",[1058,3496,3102],{"class":1218},[1058,3498,1229],{"class":1068},[1058,3500,1167],{"class":1068},[1058,3502,1069],{"class":1068},[1058,3504,3505],{"class":1218}," minTier",[1058,3507,1167],{"class":1068},[1058,3509,2038],{"class":1239},[1058,3511,1133],{"class":1068},[1058,3513,3514],{"class":1218}," factors",[1058,3516,1167],{"class":1068},[1058,3518,3519],{"class":1072}," [",[1058,3521,1127],{"class":1068},[1058,3523,3524],{"class":1218}," anyOf",[1058,3526,1167],{"class":1068},[1058,3528,3519],{"class":1072},[1058,3530,1229],{"class":1068},[1058,3532,333],{"class":1085},[1058,3534,1229],{"class":1068},[1058,3536,3537],{"class":1072},"] ",[1058,3539,1139],{"class":1068},[1058,3541,3537],{"class":1072},[1058,3543,2433],{"class":1068},[1058,3545,3546],{"class":1060,"line":2162},[1058,3547,3548],{"class":1068},"    },\n",[1058,3550,3551],{"class":1060,"line":2176},[1058,3552,3553],{"class":1068},"  },\n",[1058,3555,3556,3558],{"class":1060,"line":2186},[1058,3557,1139],{"class":1068},[1058,3559,1142],{"class":1072},[1573,3561,3563],{"id":3562},"checkgate","checkGate()",[1049,3565,3567],{"className":1051,"code":3566,"language":1053,"meta":1054,"style":1054},"import { PolicyDeniedError } from '@noy-db\u002Fhub'\n\ntry {\n  await db.checkGate('acme', 'app:approve-large-payment', {\n    factors: [{ kind: 'totp', payload: { code: '123456' }, mintedAt: new Date().toISOString() }],\n  })\n  await invoices.put('inv-002', payment)\n} catch (err) {\n  if (err instanceof PolicyDeniedError) {\n    \u002F\u002F err.gate, err.required, and err.reason:\n    \u002F\u002F   'insufficient-tier' | 'missing-factor' | 'stale-proof' | 'disabled' | 'shared-device-blocked'\n  }\n}\n",[957,3568,3569,3588,3592,3599,3631,3706,3713,3741,3753,3773,3778,3783,3788],{"__ignoreMap":1054},[1058,3570,3571,3573,3575,3578,3580,3582,3584,3586],{"class":1060,"line":1061},[1058,3572,1065],{"class":1064},[1058,3574,1069],{"class":1068},[1058,3576,3577],{"class":1072}," PolicyDeniedError",[1058,3579,1076],{"class":1068},[1058,3581,1079],{"class":1064},[1058,3583,1082],{"class":1068},[1058,3585,992],{"class":1085},[1058,3587,1088],{"class":1068},[1058,3589,3590],{"class":1060,"line":1091},[1058,3591,1095],{"emptyLinePlaceholder":1094},[1058,3593,3594,3597],{"class":1060,"line":1098},[1058,3595,3596],{"class":1064},"try",[1058,3598,1968],{"class":1068},[1058,3600,3601,3604,3606,3608,3611,3613,3615,3617,3619,3621,3623,3625,3627,3629],{"class":1060,"line":1105},[1058,3602,3603],{"class":1064},"  await",[1058,3605,1263],{"class":1072},[1058,3607,968],{"class":1068},[1058,3609,3610],{"class":1121},"checkGate",[1058,3612,1124],{"class":1218},[1058,3614,1229],{"class":1068},[1058,3616,1279],{"class":1085},[1058,3618,1229],{"class":1068},[1058,3620,1133],{"class":1068},[1058,3622,1082],{"class":1068},[1058,3624,3102],{"class":1085},[1058,3626,1229],{"class":1068},[1058,3628,1133],{"class":1068},[1058,3630,1968],{"class":1068},[1058,3632,3633,3636,3638,3640,3642,3645,3647,3649,3651,3653,3655,3658,3660,3662,3665,3667,3669,3672,3674,3677,3680,3682,3685,3688,3691,3693,3696,3699,3701,3704],{"class":1060,"line":1762},[1058,3634,3635],{"class":1218},"    factors",[1058,3637,1167],{"class":1068},[1058,3639,3519],{"class":1218},[1058,3641,1127],{"class":1068},[1058,3643,3644],{"class":1218}," kind",[1058,3646,1167],{"class":1068},[1058,3648,1082],{"class":1068},[1058,3650,333],{"class":1085},[1058,3652,1229],{"class":1068},[1058,3654,1133],{"class":1068},[1058,3656,3657],{"class":1218}," payload",[1058,3659,1167],{"class":1068},[1058,3661,1069],{"class":1068},[1058,3663,3664],{"class":1218}," code",[1058,3666,1167],{"class":1068},[1058,3668,1082],{"class":1068},[1058,3670,3671],{"class":1085},"123456",[1058,3673,1229],{"class":1068},[1058,3675,3676],{"class":1068}," },",[1058,3678,3679],{"class":1218}," mintedAt",[1058,3681,1167],{"class":1068},[1058,3683,3684],{"class":1068}," new",[1058,3686,3687],{"class":1121}," Date",[1058,3689,3690],{"class":1218},"()",[1058,3692,968],{"class":1068},[1058,3694,3695],{"class":1121},"toISOString",[1058,3697,3698],{"class":1218},"() ",[1058,3700,1139],{"class":1068},[1058,3702,3703],{"class":1218},"]",[1058,3705,2377],{"class":1068},[1058,3707,3708,3711],{"class":1060,"line":1767},[1058,3709,3710],{"class":1068},"  }",[1058,3712,1142],{"class":1218},[1058,3714,3715,3717,3720,3722,3725,3727,3729,3732,3734,3736,3739],{"class":1060,"line":1783},[1058,3716,3603],{"class":1064},[1058,3718,3719],{"class":1072}," invoices",[1058,3721,968],{"class":1068},[1058,3723,3724],{"class":1121},"put",[1058,3726,1124],{"class":1218},[1058,3728,1229],{"class":1068},[1058,3730,3731],{"class":1085},"inv-002",[1058,3733,1229],{"class":1068},[1058,3735,1133],{"class":1068},[1058,3737,3738],{"class":1072}," payment",[1058,3740,1142],{"class":1218},[1058,3742,3743,3745,3748,3751],{"class":1060,"line":1789},[1058,3744,1139],{"class":1068},[1058,3746,3747],{"class":1064}," catch",[1058,3749,3750],{"class":1072}," (err) ",[1058,3752,3441],{"class":1068},[1058,3754,3755,3758,3760,3763,3766,3768,3771],{"class":1060,"line":1794},[1058,3756,3757],{"class":1064},"  if",[1058,3759,1824],{"class":1218},[1058,3761,3762],{"class":1072},"err",[1058,3764,3765],{"class":1068}," instanceof",[1058,3767,3577],{"class":1964},[1058,3769,3770],{"class":1218},") ",[1058,3772,3441],{"class":1068},[1058,3774,3775],{"class":1060,"line":1810},[1058,3776,3777],{"class":1101},"    \u002F\u002F err.gate, err.required, and err.reason:\n",[1058,3779,3780],{"class":1060,"line":2103},[1058,3781,3782],{"class":1101},"    \u002F\u002F   'insufficient-tier' | 'missing-factor' | 'stale-proof' | 'disabled' | 'shared-device-blocked'\n",[1058,3784,3785],{"class":1060,"line":2119},[1058,3786,3787],{"class":1068},"  }\n",[1058,3789,3790],{"class":1060,"line":2130},[1058,3791,2073],{"class":1068},[1573,3793,3795],{"id":3794},"per-keyring-policy-override-reserved","Per-keyring policy override — reserved",[940,3797,3798,3799,3802,3803,3806,3807,3809],{},"The on-disk keyring format has a ",[957,3800,3801],{},"policy?: VaultPolicy"," field reserved for a future per-keyring override (a keyring-level policy that can only ",[948,3804,3805],{},"strengthen",", never weaken, the vault-level floor). Today that field is parsed and round-tripped but not enforced — the vault's ",[957,3808,2879],{}," document is the sole source of truth. No API exists yet for the merge behavior; adding it later will not require a vault-format migration.",[1468,3811,3813],{"id":3812},"rotate-and-recover-the-secret","Rotate and recover the secret",[1573,3815,3817],{"id":3816},"rotate-user-remembers-the-old-secret","Rotate (user remembers the old secret)",[1049,3819,3821],{"className":1051,"code":3820,"language":1053,"meta":1054,"style":1054},"await db.team.rotateSecret('acme', {\n  oldSecret: 'correct horse battery staple printer toaster',\n  newSecret: 'glasses cabinet bicycle umbrella thunder velvet',\n}, {\n  factors: [{ kind: 'totp', payload: { code: '123456' } }],\n})\n",[957,3822,3823,3850,3865,3881,3888,3934],{"__ignoreMap":1054},[1058,3824,3825,3827,3829,3831,3833,3835,3838,3840,3842,3844,3846,3848],{"class":1060,"line":1061},[1058,3826,1260],{"class":1064},[1058,3828,1263],{"class":1072},[1058,3830,968],{"class":1068},[1058,3832,868],{"class":1072},[1058,3834,968],{"class":1068},[1058,3836,3837],{"class":1121},"rotateSecret",[1058,3839,1124],{"class":1072},[1058,3841,1229],{"class":1068},[1058,3843,1279],{"class":1085},[1058,3845,1229],{"class":1068},[1058,3847,1133],{"class":1068},[1058,3849,1968],{"class":1068},[1058,3851,3852,3855,3857,3859,3861,3863],{"class":1060,"line":1091},[1058,3853,3854],{"class":1218},"  oldSecret",[1058,3856,1167],{"class":1068},[1058,3858,1082],{"class":1068},[1058,3860,1750],{"class":1085},[1058,3862,1229],{"class":1068},[1058,3864,2377],{"class":1068},[1058,3866,3867,3870,3872,3874,3877,3879],{"class":1060,"line":1098},[1058,3868,3869],{"class":1218},"  newSecret",[1058,3871,1167],{"class":1068},[1058,3873,1082],{"class":1068},[1058,3875,3876],{"class":1085},"glasses cabinet bicycle umbrella thunder velvet",[1058,3878,1229],{"class":1068},[1058,3880,2377],{"class":1068},[1058,3882,3883,3886],{"class":1060,"line":1105},[1058,3884,3885],{"class":1068},"},",[1058,3887,1968],{"class":1068},[1058,3889,3890,3892,3894,3896,3898,3900,3902,3904,3906,3908,3910,3912,3914,3916,3918,3920,3922,3924,3926,3928,3930,3932],{"class":1060,"line":1762},[1058,3891,2981],{"class":1218},[1058,3893,1167],{"class":1068},[1058,3895,3519],{"class":1072},[1058,3897,1127],{"class":1068},[1058,3899,3644],{"class":1218},[1058,3901,1167],{"class":1068},[1058,3903,1082],{"class":1068},[1058,3905,333],{"class":1085},[1058,3907,1229],{"class":1068},[1058,3909,1133],{"class":1068},[1058,3911,3657],{"class":1218},[1058,3913,1167],{"class":1068},[1058,3915,1069],{"class":1068},[1058,3917,3664],{"class":1218},[1058,3919,1167],{"class":1068},[1058,3921,1082],{"class":1068},[1058,3923,3671],{"class":1085},[1058,3925,1229],{"class":1068},[1058,3927,1076],{"class":1068},[1058,3929,1076],{"class":1068},[1058,3931,3703],{"class":1072},[1058,3933,2377],{"class":1068},[1058,3935,3936,3938],{"class":1060,"line":1767},[1058,3937,1139],{"class":1068},[1058,3939,1142],{"class":1072},[940,3941,3942,3943,3946,3947,3950],{},"The hub re-derives the old KEK from ",[957,3944,3945],{},"oldSecret"," (this also validates it — a wrong secret throws ",[957,3948,3949],{},"InvalidKeyError","), validates the new secret, generates a fresh salt and KEK, rewraps every DEK, and persists.",[940,3952,3953,3956,3957,3960],{},[943,3954,3955],{},"Tier-2 slots are dropped by default on rotation"," — each slot wraps material derived under the old KEK, and the hub has no way to re-derive that without the method-specific ceremony. To preserve a slot across rotation, supply a ",[957,3958,3959],{},"slotCeremonies"," entry keyed by slot id; the ceremony re-derives the slot's wrapping under the new keyring:",[1049,3962,3964],{"className":1051,"code":3963,"language":1053,"meta":1054,"style":1054},"await db.team.rotateSecret('acme', {\n  oldSecret, newSecret,\n  slotCeremonies: {\n    'webauthn-yubikey-blue': async (ctx) => webAuthnSlotRewrapCeremony(\u002F* ... *\u002F)(ctx),\n  },\n})\n\u002F\u002F → slots omitted from `slotCeremonies` are dropped; re-enroll them via db.enrollAuthenticator\n",[957,3965,3966,3992,4003,4012,4047,4051,4057],{"__ignoreMap":1054},[1058,3967,3968,3970,3972,3974,3976,3978,3980,3982,3984,3986,3988,3990],{"class":1060,"line":1061},[1058,3969,1260],{"class":1064},[1058,3971,1263],{"class":1072},[1058,3973,968],{"class":1068},[1058,3975,868],{"class":1072},[1058,3977,968],{"class":1068},[1058,3979,3837],{"class":1121},[1058,3981,1124],{"class":1072},[1058,3983,1229],{"class":1068},[1058,3985,1279],{"class":1085},[1058,3987,1229],{"class":1068},[1058,3989,1133],{"class":1068},[1058,3991,1968],{"class":1068},[1058,3993,3994,3996,3998,4001],{"class":1060,"line":1091},[1058,3995,3854],{"class":1072},[1058,3997,1133],{"class":1068},[1058,3999,4000],{"class":1072}," newSecret",[1058,4002,2377],{"class":1068},[1058,4004,4005,4008,4010],{"class":1060,"line":1098},[1058,4006,4007],{"class":1218},"  slotCeremonies",[1058,4009,1167],{"class":1068},[1058,4011,1968],{"class":1068},[1058,4013,4014,4017,4019,4021,4023,4025,4027,4030,4032,4034,4037,4039,4042,4045],{"class":1060,"line":1105},[1058,4015,4016],{"class":1068},"    '",[1058,4018,2372],{"class":1218},[1058,4020,1229],{"class":1068},[1058,4022,1167],{"class":1068},[1058,4024,2605],{"class":1108},[1058,4026,1824],{"class":1068},[1058,4028,4029],{"class":2610},"ctx",[1058,4031,2614],{"class":1068},[1058,4033,2617],{"class":1108},[1058,4035,4036],{"class":1121}," webAuthnSlotRewrapCeremony",[1058,4038,1124],{"class":1072},[1058,4040,4041],{"class":1101},"\u002F* ... *\u002F",[1058,4043,4044],{"class":1072},")(ctx)",[1058,4046,2377],{"class":1068},[1058,4048,4049],{"class":1060,"line":1762},[1058,4050,3553],{"class":1068},[1058,4052,4053,4055],{"class":1060,"line":1767},[1058,4054,1139],{"class":1068},[1058,4056,1142],{"class":1072},[1058,4058,4059],{"class":1060,"line":1783},[1058,4060,4061],{"class":1101},"\u002F\u002F → slots omitted from `slotCeremonies` are dropped; re-enroll them via db.enrollAuthenticator\n",[1573,4063,4065],{"id":4064},"recover-user-forgot-the-secret","Recover (user forgot the secret)",[940,4067,4068,4069,4072,4073,4075],{},"The vault needs at least one ",[943,4070,4071],{},"recovery profile"," enrolled; without one, ",[957,4074,3165],{}," is disabled and recovery is impossible.",[1292,4077,4078,4091],{},[1295,4079,4080],{},[1298,4081,4082,4085,4088],{},[1301,4083,4084],{},"Profile",[1301,4086,4087],{},"Mechanism",[1301,4089,4090],{},"Status",[1317,4092,4093,4106,4128,4144],{},[1298,4094,4095,4100,4103],{},[1322,4096,4097],{},[943,4098,4099],{},"paper",[1322,4101,4102],{},"Single-use codes; a matching code unwraps the cached DEKs, then rewraps them under a fresh KEK.",[1322,4104,4105],{},"Implemented end to end.",[1298,4107,4108,4112,4126],{},[1322,4109,4110],{},[943,4111,325],{},[1322,4113,4114,4115,4118,4119,4122,4123,4125],{},"K-of-N shares reconstruct the DEK set; requires a ",[957,4116,4117],{},"NoydbShamir"," (from ",[957,4120,4121],{},"@noy-db\u002Fon-shamir",") at ",[957,4124,1827],{}," time.",[1322,4127,4105],{},[1298,4129,4130,4135,4138],{},[1322,4131,4132],{},[943,4133,4134],{},"multi-channel",[1322,4136,4137],{},"Reserved for a 2-of-3 email\u002FPIN\u002Fpaper recovery combination.",[1322,4139,4140,4141,968],{},"No API yet — calling it throws ",[957,4142,4143],{},"RecoveryProfileNotImplementedError",[1298,4145,4146,4151,4154],{},[1322,4147,4148],{},[943,4149,4150],{},"admin-mediated",[1322,4152,4153],{},"Reserved for an admin-issued reset that preserves zero-knowledge.",[1322,4155,4140,4156,968],{},[957,4157,4143],{},[1049,4159,4161],{"className":1051,"code":4160,"language":1053,"meta":1054,"style":1054},"await db.team.recoverSecret('acme', {\n  newSecret: 'glasses cabinet bicycle umbrella thunder velvet',\n  recoveryProof: { profile: 'paper', payload: { code: 'ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ12' } },\n})\n\u002F\u002F → { newCodes: [...] }  — by default the remaining paper codes are burned and reissued;\n\u002F\u002F   pass { rotateRemainingCodes: false } to keep the unused codes valid\n",[957,4162,4163,4190,4204,4248,4254,4259],{"__ignoreMap":1054},[1058,4164,4165,4167,4169,4171,4173,4175,4178,4180,4182,4184,4186,4188],{"class":1060,"line":1061},[1058,4166,1260],{"class":1064},[1058,4168,1263],{"class":1072},[1058,4170,968],{"class":1068},[1058,4172,868],{"class":1072},[1058,4174,968],{"class":1068},[1058,4176,4177],{"class":1121},"recoverSecret",[1058,4179,1124],{"class":1072},[1058,4181,1229],{"class":1068},[1058,4183,1279],{"class":1085},[1058,4185,1229],{"class":1068},[1058,4187,1133],{"class":1068},[1058,4189,1968],{"class":1068},[1058,4191,4192,4194,4196,4198,4200,4202],{"class":1060,"line":1091},[1058,4193,3869],{"class":1218},[1058,4195,1167],{"class":1068},[1058,4197,1082],{"class":1068},[1058,4199,3876],{"class":1085},[1058,4201,1229],{"class":1068},[1058,4203,2377],{"class":1068},[1058,4205,4206,4209,4211,4213,4216,4218,4220,4222,4224,4226,4228,4230,4232,4234,4236,4238,4241,4243,4245],{"class":1060,"line":1098},[1058,4207,4208],{"class":1218},"  recoveryProof",[1058,4210,1167],{"class":1068},[1058,4212,1069],{"class":1068},[1058,4214,4215],{"class":1218}," profile",[1058,4217,1167],{"class":1068},[1058,4219,1082],{"class":1068},[1058,4221,4099],{"class":1085},[1058,4223,1229],{"class":1068},[1058,4225,1133],{"class":1068},[1058,4227,3657],{"class":1218},[1058,4229,1167],{"class":1068},[1058,4231,1069],{"class":1068},[1058,4233,3664],{"class":1218},[1058,4235,1167],{"class":1068},[1058,4237,1082],{"class":1068},[1058,4239,4240],{"class":1085},"ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ12",[1058,4242,1229],{"class":1068},[1058,4244,1076],{"class":1068},[1058,4246,4247],{"class":1068}," },\n",[1058,4249,4250,4252],{"class":1060,"line":1105},[1058,4251,1139],{"class":1068},[1058,4253,1142],{"class":1072},[1058,4255,4256],{"class":1060,"line":1762},[1058,4257,4258],{"class":1101},"\u002F\u002F → { newCodes: [...] }  — by default the remaining paper codes are burned and reissued;\n",[1058,4260,4261],{"class":1060,"line":1767},[1058,4262,4263],{"class":1101},"\u002F\u002F   pass { rotateRemainingCodes: false } to keep the unused codes valid\n",[940,4265,4266,4267,4269],{},"Enroll recovery at vault creation so ",[957,4268,3165],{}," is never left disabled by accident:",[1049,4271,4273],{"className":1051,"code":4272,"language":1053,"meta":1054,"style":1054},"await createNoydb({\n  store,\n  recovery: [\n    { profile: 'paper', codes: 10 },\n    \u002F\u002F or: { profile: 'shamir', k: 2, n: 3, trustees: ['alice@acme.com', 'bob@acme.com'] },\n  ],\n})\n",[957,4274,4275,4285,4291,4301,4328,4333,4340],{"__ignoreMap":1054},[1058,4276,4277,4279,4281,4283],{"class":1060,"line":1061},[1058,4278,1260],{"class":1064},[1058,4280,1073],{"class":1121},[1058,4282,1124],{"class":1072},[1058,4284,3441],{"class":1068},[1058,4286,4287,4289],{"class":1060,"line":1091},[1058,4288,3446],{"class":1072},[1058,4290,2377],{"class":1068},[1058,4292,4293,4296,4298],{"class":1060,"line":1098},[1058,4294,4295],{"class":1218},"  recovery",[1058,4297,1167],{"class":1068},[1058,4299,4300],{"class":1072}," [\n",[1058,4302,4303,4306,4308,4310,4312,4314,4316,4318,4321,4323,4326],{"class":1060,"line":1105},[1058,4304,4305],{"class":1068},"    {",[1058,4307,4215],{"class":1218},[1058,4309,1167],{"class":1068},[1058,4311,1082],{"class":1068},[1058,4313,4099],{"class":1085},[1058,4315,1229],{"class":1068},[1058,4317,1133],{"class":1068},[1058,4319,4320],{"class":1218}," codes",[1058,4322,1167],{"class":1068},[1058,4324,4325],{"class":1239}," 10",[1058,4327,4247],{"class":1068},[1058,4329,4330],{"class":1060,"line":1762},[1058,4331,4332],{"class":1101},"    \u002F\u002F or: { profile: 'shamir', k: 2, n: 3, trustees: ['alice@acme.com', 'bob@acme.com'] },\n",[1058,4334,4335,4338],{"class":1060,"line":1767},[1058,4336,4337],{"class":1072},"  ]",[1058,4339,2377],{"class":1068},[1058,4341,4342,4344],{"class":1060,"line":1783},[1058,4343,1139],{"class":1068},[1058,4345,1142],{"class":1072},[940,4347,4348,4349,4351,4352,4354,4355,4357,4358,968],{},"If ",[957,4350,321],{}," is omitted and the ",[957,4353,3165],{}," gate is not explicitly disabled, ",[957,4356,1827],{}," throws ",[957,4359,4360],{},"RecoveryNotEnrolledError",[1468,4362,4364],{"id":4363},"tier-1-echo-secret","Tier 1 — Echo secret",[940,4366,4367],{},[948,4368,4369,4370,968],{},"Available since ",[957,4371,4372],{},"0.6.0",[940,4374,4375,4376,4379,4380,1167],{},"Selected at vault creation with ",[957,4377,4378],{},"secretMode: 'echo'",", this mode makes the tier-1 prompt resistant to phishing. The secret is ",[943,4381,4382],{},"one memorable sentence enrolled as three parts",[1049,4384,4387],{"className":4385,"code":4386,"language":1670},[1668],"prompt (the user types)  →  echo (the vault reveals)  →  key (the user types)\n\"mi chiamo vicio\"        →  \"ma da piccolo al santanna mi chiamavano\"  →  \"ciccio\"\n",[957,4388,4386],{"__ignoreMap":1054},[940,4390,4391,4392,4395,4396,4398,4399,4402,4403,4406,4407,4410],{},"At unlock the user types only the ",[943,4393,4394],{},"prompt",". The vault answers by revealing the ",[943,4397,1532],{}," — the middle of the user's own sentence, which nothing but a keyring holding that secret can produce. The user recognizes it and finishes with the ",[943,4400,4401],{},"key",". This is ",[943,4404,4405],{},"mutual authentication",": the vault proves it holds the secret ",[948,4408,4409],{},"before"," the user hands over the rest of theirs. A phishing page that replays a convincing-looking unlock screen has nothing to show at the reveal step, and there is no single-field secret entry it could imitate instead — an echo keyring cannot be opened by any single typed string.",[940,4412,4413,4414,4417],{},"The echo doubles as a ",[943,4415,4416],{},"memory aid",": tier 1 is typed so rarely that forgetting it is the dominant real-world failure, and the revealed middle of your own sentence is a retrieval cue for its ending.",[940,4419,4420],{},"Echo is orthogonal to the other two modes: it is still tier 1, still derives the KEK, and tier-2 slots and tier-3 PINs enroll from an echo session exactly as from a standard one.",[1573,4422,4424],{"id":4423},"enroll","Enroll",[1049,4426,4428],{"className":1051,"code":4427,"language":1053,"meta":1054,"style":1054},"import { createNoydb } from '@noy-db\u002Fhub'\n\nconst db = await createNoydb({\n  store,\n  user: 'vicio',\n  secretMode: 'echo',\n  secret: {\n    prompt: 'mi chiamo vicio',\n    echo: 'ma da piccolo al santanna mi chiamavano',\n    key: 'ciccio',\n  },\n  echoMaskHint: 'first-letters',   \u002F\u002F optional: how the player should mask the echo on screen\n  validateSecret: true,\n})\n\nconst vault = await db.openVault('acme')   \u002F\u002F the keyring — and its echo block — is written here\n",[957,4429,4430,4448,4452,4468,4474,4490,4505,4513,4529,4545,4561,4565,4584,4595,4601,4605],{"__ignoreMap":1054},[1058,4431,4432,4434,4436,4438,4440,4442,4444,4446],{"class":1060,"line":1061},[1058,4433,1065],{"class":1064},[1058,4435,1069],{"class":1068},[1058,4437,1073],{"class":1072},[1058,4439,1076],{"class":1068},[1058,4441,1079],{"class":1064},[1058,4443,1082],{"class":1068},[1058,4445,992],{"class":1085},[1058,4447,1088],{"class":1068},[1058,4449,4450],{"class":1060,"line":1091},[1058,4451,1095],{"emptyLinePlaceholder":1094},[1058,4453,4454,4456,4458,4460,4462,4464,4466],{"class":1060,"line":1098},[1058,4455,1109],{"class":1108},[1058,4457,1112],{"class":1072},[1058,4459,1115],{"class":1068},[1058,4461,1118],{"class":1064},[1058,4463,1073],{"class":1121},[1058,4465,1124],{"class":1072},[1058,4467,3441],{"class":1068},[1058,4469,4470,4472],{"class":1060,"line":1105},[1058,4471,3446],{"class":1072},[1058,4473,2377],{"class":1068},[1058,4475,4476,4479,4481,4483,4486,4488],{"class":1060,"line":1762},[1058,4477,4478],{"class":1218},"  user",[1058,4480,1167],{"class":1068},[1058,4482,1082],{"class":1068},[1058,4484,4485],{"class":1085},"vicio",[1058,4487,1229],{"class":1068},[1058,4489,2377],{"class":1068},[1058,4491,4492,4495,4497,4499,4501,4503],{"class":1060,"line":1767},[1058,4493,4494],{"class":1218},"  secretMode",[1058,4496,1167],{"class":1068},[1058,4498,1082],{"class":1068},[1058,4500,1532],{"class":1085},[1058,4502,1229],{"class":1068},[1058,4504,2377],{"class":1068},[1058,4506,4507,4509,4511],{"class":1060,"line":1783},[1058,4508,2906],{"class":1218},[1058,4510,1167],{"class":1068},[1058,4512,1968],{"class":1068},[1058,4514,4515,4518,4520,4522,4525,4527],{"class":1060,"line":1789},[1058,4516,4517],{"class":1218},"    prompt",[1058,4519,1167],{"class":1068},[1058,4521,1082],{"class":1068},[1058,4523,4524],{"class":1085},"mi chiamo vicio",[1058,4526,1229],{"class":1068},[1058,4528,2377],{"class":1068},[1058,4530,4531,4534,4536,4538,4541,4543],{"class":1060,"line":1794},[1058,4532,4533],{"class":1218},"    echo",[1058,4535,1167],{"class":1068},[1058,4537,1082],{"class":1068},[1058,4539,4540],{"class":1085},"ma da piccolo al santanna mi chiamavano",[1058,4542,1229],{"class":1068},[1058,4544,2377],{"class":1068},[1058,4546,4547,4550,4552,4554,4557,4559],{"class":1060,"line":1810},[1058,4548,4549],{"class":1218},"    key",[1058,4551,1167],{"class":1068},[1058,4553,1082],{"class":1068},[1058,4555,4556],{"class":1085},"ciccio",[1058,4558,1229],{"class":1068},[1058,4560,2377],{"class":1068},[1058,4562,4563],{"class":1060,"line":2103},[1058,4564,3553],{"class":1068},[1058,4566,4567,4570,4572,4574,4577,4579,4581],{"class":1060,"line":2119},[1058,4568,4569],{"class":1218},"  echoMaskHint",[1058,4571,1167],{"class":1068},[1058,4573,1082],{"class":1068},[1058,4575,4576],{"class":1085},"first-letters",[1058,4578,1229],{"class":1068},[1058,4580,1133],{"class":1068},[1058,4582,4583],{"class":1101},"   \u002F\u002F optional: how the player should mask the echo on screen\n",[1058,4585,4586,4589,4591,4593],{"class":1060,"line":2130},[1058,4587,4588],{"class":1218},"  validateSecret",[1058,4590,1167],{"class":1068},[1058,4592,1902],{"class":1901},[1058,4594,2377],{"class":1068},[1058,4596,4597,4599],{"class":1060,"line":2135},[1058,4598,1139],{"class":1068},[1058,4600,1142],{"class":1072},[1058,4602,4603],{"class":1060,"line":2140},[1058,4604,1095],{"emptyLinePlaceholder":1094},[1058,4606,4607,4609,4612,4614,4616,4618,4620,4623,4625,4627,4629,4631,4634],{"class":1060,"line":2146},[1058,4608,1109],{"class":1108},[1058,4610,4611],{"class":1072}," vault ",[1058,4613,1115],{"class":1068},[1058,4615,1118],{"class":1064},[1058,4617,1263],{"class":1072},[1058,4619,968],{"class":1068},[1058,4621,4622],{"class":1121},"openVault",[1058,4624,1124],{"class":1072},[1058,4626,1229],{"class":1068},[1058,4628,1279],{"class":1085},[1058,4630,1229],{"class":1068},[1058,4632,4633],{"class":1072},")   ",[1058,4635,4636],{"class":1101},"\u002F\u002F the keyring — and its echo block — is written here\n",[940,4638,4639,4641,4642,4645,4646,4648,4649,4652,4653,4655,4656,4659,4660,1380,4663,1380,4666,4669],{},[957,4640,983],{}," takes a ",[957,4643,4644],{},"{ prompt, echo, key }"," struct instead of a string, and the two must agree: a string under ",[957,4647,4378],{},", or a struct without it, throws ",[957,4650,4651],{},"ValidationError"," before any store call. ",[957,4654,4378],{}," is mutually exclusive with ",[957,4657,4658],{},"sealingKey"," (that is managed mode), and ",[957,4661,4662],{},"deviceSeal",[957,4664,4665],{},"echoMaskHint",[957,4667,4668],{},"echoSecretPolicy"," are rejected outside echo mode.",[940,4671,4672],{},"Reopening a vault when the app already holds all three parts needs no ceremony — pass the same struct again:",[1049,4674,4676],{"className":1051,"code":4675,"language":1053,"meta":1054,"style":1054},"const db = await createNoydb({ store, user: 'vicio', secretMode: 'echo', secret: parts })\n",[957,4677,4678],{"__ignoreMap":1054},[1058,4679,4680,4682,4684,4686,4688,4690,4692,4694,4696,4698,4701,4703,4705,4707,4709,4711,4714,4716,4718,4720,4722,4724,4727,4729,4732,4734],{"class":1060,"line":1061},[1058,4681,1109],{"class":1108},[1058,4683,1112],{"class":1072},[1058,4685,1115],{"class":1068},[1058,4687,1118],{"class":1064},[1058,4689,1073],{"class":1121},[1058,4691,1124],{"class":1072},[1058,4693,1127],{"class":1068},[1058,4695,1130],{"class":1072},[1058,4697,1133],{"class":1068},[1058,4699,4700],{"class":1218}," user",[1058,4702,1167],{"class":1068},[1058,4704,1082],{"class":1068},[1058,4706,4485],{"class":1085},[1058,4708,1229],{"class":1068},[1058,4710,1133],{"class":1068},[1058,4712,4713],{"class":1218}," secretMode",[1058,4715,1167],{"class":1068},[1058,4717,1082],{"class":1068},[1058,4719,1532],{"class":1085},[1058,4721,1229],{"class":1068},[1058,4723,1133],{"class":1068},[1058,4725,4726],{"class":1218}," secret",[1058,4728,1167],{"class":1068},[1058,4730,4731],{"class":1072}," parts ",[1058,4733,1139],{"class":1068},[1058,4735,1142],{"class":1072},[940,4737,4738],{},"That is the unattended path (tests, servers, a headless job). Interactive apps use the ceremony below, because the point of echo mode is the reveal.",[1573,4740,4742],{"id":4741},"the-reveal-blob-portable-sealed-or-none","The reveal blob: portable, sealed, or none",[940,4744,4745,4746,1167],{},"The echo has to come from somewhere at unlock time. Each keyring records one ",[943,4747,4748],{},"reveal policy",[1292,4750,4751,4766],{},[1295,4752,4753],{},[1298,4754,4755,4760,4763],{},[1301,4756,4757,3644],{},[957,4758,4759],{},"reveal",[1301,4761,4762],{},"Where the echo lives",[1301,4764,4765],{},"Enrolled by",[1317,4767,4768,4781,4804],{},[1298,4769,4770,4775,4778],{},[1322,4771,4772],{},[957,4773,4774],{},"portable",[1322,4776,4777],{},"encrypted in the keyring under a key derived from the prompt — travels with the file",[1322,4779,4780],{},"the default for live keyrings and for pod recipients",[1298,4782,4783,4788,4791],{},[1322,4784,4785],{},[957,4786,4787],{},"sealed",[1322,4789,4790],{},"sealed by a device-local key store; the keyring holds opaque bytes plus the sealing provider's id",[1322,4792,4793,4794,4796,4797,4799,4800,4803],{},"passing ",[957,4795,4662],{}," at ",[957,4798,1827],{}," (or ",[957,4801,4802],{},"echoOptions.deviceSeal"," on rotation)",[1298,4805,4806,4811,4814],{},[1322,4807,4808],{},[957,4809,4810],{},"none",[1322,4812,4813],{},"nowhere — the ceremony asks the user to type the echo, and verifies it",[1322,4815,4816,4817],{},"opting a pod recipient into ",[957,4818,4819],{},"reveal: 'none'",[1049,4821,4823],{"className":1051,"code":4822,"language":1053,"meta":1054,"style":1054},"import { createNoydb, MemoryDeviceSeal } from '@noy-db\u002Fhub'\n\nconst db = await createNoydb({\n  store, user: 'vicio', secretMode: 'echo', secret: parts,\n  deviceSeal: new MemoryDeviceSeal({ id: 'device-1' }),\n})\n\u002F\u002F → the owner keyring enrolls reveal: 'sealed', pinned to provider id 'device-1'\n",[957,4824,4825,4848,4852,4868,4907,4940,4946],{"__ignoreMap":1054},[1058,4826,4827,4829,4831,4833,4835,4838,4840,4842,4844,4846],{"class":1060,"line":1061},[1058,4828,1065],{"class":1064},[1058,4830,1069],{"class":1068},[1058,4832,1073],{"class":1072},[1058,4834,1133],{"class":1068},[1058,4836,4837],{"class":1072}," MemoryDeviceSeal",[1058,4839,1076],{"class":1068},[1058,4841,1079],{"class":1064},[1058,4843,1082],{"class":1068},[1058,4845,992],{"class":1085},[1058,4847,1088],{"class":1068},[1058,4849,4850],{"class":1060,"line":1091},[1058,4851,1095],{"emptyLinePlaceholder":1094},[1058,4853,4854,4856,4858,4860,4862,4864,4866],{"class":1060,"line":1098},[1058,4855,1109],{"class":1108},[1058,4857,1112],{"class":1072},[1058,4859,1115],{"class":1068},[1058,4861,1118],{"class":1064},[1058,4863,1073],{"class":1121},[1058,4865,1124],{"class":1072},[1058,4867,3441],{"class":1068},[1058,4869,4870,4872,4874,4876,4878,4880,4882,4884,4886,4888,4890,4892,4894,4896,4898,4900,4902,4905],{"class":1060,"line":1105},[1058,4871,3446],{"class":1072},[1058,4873,1133],{"class":1068},[1058,4875,4700],{"class":1218},[1058,4877,1167],{"class":1068},[1058,4879,1082],{"class":1068},[1058,4881,4485],{"class":1085},[1058,4883,1229],{"class":1068},[1058,4885,1133],{"class":1068},[1058,4887,4713],{"class":1218},[1058,4889,1167],{"class":1068},[1058,4891,1082],{"class":1068},[1058,4893,1532],{"class":1085},[1058,4895,1229],{"class":1068},[1058,4897,1133],{"class":1068},[1058,4899,4726],{"class":1218},[1058,4901,1167],{"class":1068},[1058,4903,4904],{"class":1072}," parts",[1058,4906,2377],{"class":1068},[1058,4908,4909,4912,4914,4916,4918,4920,4922,4925,4927,4929,4932,4934,4936,4938],{"class":1060,"line":1762},[1058,4910,4911],{"class":1218},"  deviceSeal",[1058,4913,1167],{"class":1068},[1058,4915,3684],{"class":1068},[1058,4917,4837],{"class":1121},[1058,4919,1124],{"class":1072},[1058,4921,1127],{"class":1068},[1058,4923,4924],{"class":1218}," id",[1058,4926,1167],{"class":1068},[1058,4928,1082],{"class":1068},[1058,4930,4931],{"class":1085},"device-1",[1058,4933,1229],{"class":1068},[1058,4935,1076],{"class":1068},[1058,4937,2614],{"class":1072},[1058,4939,2377],{"class":1068},[1058,4941,4942,4944],{"class":1060,"line":1767},[1058,4943,1139],{"class":1068},[1058,4945,1142],{"class":1072},[1058,4947,4948],{"class":1060,"line":1783},[1058,4949,4950],{"class":1101},"\u002F\u002F → the owner keyring enrolls reveal: 'sealed', pinned to provider id 'device-1'\n",[940,4952,4953,4956,4957,4960,4961,4963,4964,4967,4968,4970,4971,4974],{},[957,4954,4955],{},"NoydbDeviceSeal"," is ",[957,4958,4959],{},"{ id, seal(bytes), unseal(bytes) }"," — the same shape as ",[957,4962,1351],{},", deliberately a distinct name because it seals only the echo reveal blob, never the secret. ",[957,4965,4966],{},"MemoryDeviceSeal"," is the only implementation shipped in ",[957,4969,992],{}," (in-memory, test-only); real ones belong in the app or an ",[957,4972,4973],{},"in-*"," binding, over IndexedDB, a file, or an OS keychain — the hub stays portable and owns no storage.",[1573,4976,4978],{"id":4977},"the-unlock-ceremony","The unlock ceremony",[1049,4980,4982],{"className":1051,"code":4981,"language":1053,"meta":1054,"style":1054},"import { beginEchoUnlock, WrongPromptError, WrongEchoError } from '@noy-db\u002Fhub'\n\nconst ceremony = await beginEchoUnlock(store, 'acme', {\n  userId: 'vicio',\n  prompt: typedPrompt,        \u002F\u002F step 1 — the only thing typed so far\n  deviceSeal,                 \u002F\u002F optional; needed to open a `sealed` reveal\n})\n\u002F\u002F ceremony.reveal   → 'ma da piccolo al santanna mi chiamavano'  (null ⇒ degraded, see below)\n\u002F\u002F ceremony.maskHint → 'first-letters' | undefined\n",[957,4983,4984,5013,5017,5045,5060,5075,5084,5090,5095],{"__ignoreMap":1054},[1058,4985,4986,4988,4990,4993,4995,4998,5000,5003,5005,5007,5009,5011],{"class":1060,"line":1061},[1058,4987,1065],{"class":1064},[1058,4989,1069],{"class":1068},[1058,4991,4992],{"class":1072}," beginEchoUnlock",[1058,4994,1133],{"class":1068},[1058,4996,4997],{"class":1072}," WrongPromptError",[1058,4999,1133],{"class":1068},[1058,5001,5002],{"class":1072}," WrongEchoError",[1058,5004,1076],{"class":1068},[1058,5006,1079],{"class":1064},[1058,5008,1082],{"class":1068},[1058,5010,992],{"class":1085},[1058,5012,1088],{"class":1068},[1058,5014,5015],{"class":1060,"line":1091},[1058,5016,1095],{"emptyLinePlaceholder":1094},[1058,5018,5019,5021,5024,5026,5028,5030,5033,5035,5037,5039,5041,5043],{"class":1060,"line":1098},[1058,5020,1109],{"class":1108},[1058,5022,5023],{"class":1072}," ceremony ",[1058,5025,1115],{"class":1068},[1058,5027,1118],{"class":1064},[1058,5029,4992],{"class":1121},[1058,5031,5032],{"class":1072},"(store",[1058,5034,1133],{"class":1068},[1058,5036,1082],{"class":1068},[1058,5038,1279],{"class":1085},[1058,5040,1229],{"class":1068},[1058,5042,1133],{"class":1068},[1058,5044,1968],{"class":1068},[1058,5046,5047,5050,5052,5054,5056,5058],{"class":1060,"line":1105},[1058,5048,5049],{"class":1218},"  userId",[1058,5051,1167],{"class":1068},[1058,5053,1082],{"class":1068},[1058,5055,4485],{"class":1085},[1058,5057,1229],{"class":1068},[1058,5059,2377],{"class":1068},[1058,5061,5062,5065,5067,5070,5072],{"class":1060,"line":1762},[1058,5063,5064],{"class":1218},"  prompt",[1058,5066,1167],{"class":1068},[1058,5068,5069],{"class":1072}," typedPrompt",[1058,5071,1133],{"class":1068},[1058,5073,5074],{"class":1101},"        \u002F\u002F step 1 — the only thing typed so far\n",[1058,5076,5077,5079,5081],{"class":1060,"line":1767},[1058,5078,4911],{"class":1072},[1058,5080,1133],{"class":1068},[1058,5082,5083],{"class":1101},"                 \u002F\u002F optional; needed to open a `sealed` reveal\n",[1058,5085,5086,5088],{"class":1060,"line":1783},[1058,5087,1139],{"class":1068},[1058,5089,1142],{"class":1072},[1058,5091,5092],{"class":1060,"line":1789},[1058,5093,5094],{"class":1101},"\u002F\u002F ceremony.reveal   → 'ma da piccolo al santanna mi chiamavano'  (null ⇒ degraded, see below)\n",[1058,5096,5097],{"class":1060,"line":1794},[1058,5098,5099],{"class":1101},"\u002F\u002F ceremony.maskHint → 'first-letters' | undefined\n",[940,5101,5102,5105,5106,5109,5110,5112,5113,5115,5116,5119],{},[957,5103,5104],{},"beginEchoUnlock"," reads the ",[957,5107,5108],{},"_keyring\u002F{userId}"," row itself, refuses an expired slot ",[948,5111,4409],{}," verifying anything, throws ",[957,5114,4651],{}," if the keyring is not an echo keyring, and throws ",[957,5117,5118],{},"WrongPromptError"," if the prompt fails its verifier. Only then does it resolve the echo.",[940,5121,5122,5123,5126],{},"Step 2 is the user's: show ",[957,5124,5125],{},"ceremony.reveal"," and let them recognize it. Step 3 completes:",[1049,5128,5130],{"className":1051,"code":5129,"language":1053,"meta":1054,"style":1054},"const unlocked = ceremony.reveal !== null\n  ? await ceremony.complete({ key: typedKey })                    \u002F\u002F recognized\n  : await ceremony.complete({ echo: typedEcho, key: typedKey })   \u002F\u002F degraded: echo typed and verified\n\nconst db = await createNoydb({ store, user: 'vicio', getKeyring: async () => unlocked })\n",[957,5131,5132,5154,5188,5228,5232],{"__ignoreMap":1054},[1058,5133,5134,5136,5138,5140,5143,5145,5148,5151],{"class":1060,"line":1061},[1058,5135,1109],{"class":1108},[1058,5137,2569],{"class":1072},[1058,5139,1115],{"class":1068},[1058,5141,5142],{"class":1072}," ceremony",[1058,5144,968],{"class":1068},[1058,5146,5147],{"class":1072},"reveal ",[1058,5149,5150],{"class":1068},"!==",[1058,5152,5153],{"class":1068}," null\n",[1058,5155,5156,5159,5161,5163,5165,5168,5170,5172,5175,5177,5180,5182,5185],{"class":1060,"line":1091},[1058,5157,5158],{"class":1068},"  ?",[1058,5160,1118],{"class":1064},[1058,5162,5142],{"class":1072},[1058,5164,968],{"class":1068},[1058,5166,5167],{"class":1121},"complete",[1058,5169,1124],{"class":1072},[1058,5171,1127],{"class":1068},[1058,5173,5174],{"class":1218}," key",[1058,5176,1167],{"class":1068},[1058,5178,5179],{"class":1072}," typedKey ",[1058,5181,1139],{"class":1068},[1058,5183,5184],{"class":1072},")                    ",[1058,5186,5187],{"class":1101},"\u002F\u002F recognized\n",[1058,5189,5190,5193,5195,5197,5199,5201,5203,5205,5208,5210,5213,5215,5217,5219,5221,5223,5225],{"class":1060,"line":1098},[1058,5191,5192],{"class":1068},"  :",[1058,5194,1118],{"class":1064},[1058,5196,5142],{"class":1072},[1058,5198,968],{"class":1068},[1058,5200,5167],{"class":1121},[1058,5202,1124],{"class":1072},[1058,5204,1127],{"class":1068},[1058,5206,5207],{"class":1218}," echo",[1058,5209,1167],{"class":1068},[1058,5211,5212],{"class":1072}," typedEcho",[1058,5214,1133],{"class":1068},[1058,5216,5174],{"class":1218},[1058,5218,1167],{"class":1068},[1058,5220,5179],{"class":1072},[1058,5222,1139],{"class":1068},[1058,5224,4633],{"class":1072},[1058,5226,5227],{"class":1101},"\u002F\u002F degraded: echo typed and verified\n",[1058,5229,5230],{"class":1060,"line":1105},[1058,5231,1095],{"emptyLinePlaceholder":1094},[1058,5233,5234,5236,5238,5240,5242,5244,5246,5248,5250,5252,5254,5256,5258,5260,5262,5264,5267,5269,5271,5274,5276,5278,5280],{"class":1060,"line":1762},[1058,5235,1109],{"class":1108},[1058,5237,1112],{"class":1072},[1058,5239,1115],{"class":1068},[1058,5241,1118],{"class":1064},[1058,5243,1073],{"class":1121},[1058,5245,1124],{"class":1072},[1058,5247,1127],{"class":1068},[1058,5249,1130],{"class":1072},[1058,5251,1133],{"class":1068},[1058,5253,4700],{"class":1218},[1058,5255,1167],{"class":1068},[1058,5257,1082],{"class":1068},[1058,5259,4485],{"class":1085},[1058,5261,1229],{"class":1068},[1058,5263,1133],{"class":1068},[1058,5265,5266],{"class":1121}," getKeyring",[1058,5268,1167],{"class":1068},[1058,5270,2605],{"class":1108},[1058,5272,5273],{"class":1068}," ()",[1058,5275,2617],{"class":1108},[1058,5277,2569],{"class":1072},[1058,5279,1139],{"class":1068},[1058,5281,1142],{"class":1072},[940,5283,5284,5287,5288,4956,5290,1437,5293,5295,5296,5298,5299,5302,5303,4956,5305,5308,5309,5311,5312,5314],{},[957,5285,5286],{},"complete()"," is stateless — a wrong key can simply be retried on the same handle. When ",[957,5289,4759],{},[957,5291,5292],{},"null",[957,5294,1532],{}," is required (omitting it throws ",[957,5297,4651],{},") and is checked against the keyring's echo verifier (",[957,5300,5301],{},"WrongEchoError"," on mismatch). When ",[957,5304,4759],{},[948,5306,5307],{},"not"," null, passing an ",[957,5310,1532],{}," that disagrees with it also throws ",[957,5313,5301],{}," rather than unlocking on a mismatched pair.",[1573,5316,5318],{"id":5317},"degradation-matrix","Degradation matrix",[940,5320,5321,5322,5325],{},"The ceremony never falls back to a weaker ",[948,5323,5324],{},"shape","; it falls back only from \"the vault reveals the echo\" to \"the user types the echo, verified\".",[1292,5327,5328,5345],{},[1295,5329,5330],{},[1298,5331,5332,5335,5339,5342],{},[1301,5333,5334],{},"Context",[1301,5336,5337,3644],{},[957,5338,4759],{},[1301,5340,5341],{},"What the user does",[1301,5343,5344],{},"What it still buys",[1317,5346,5347,5365,5382,5397,5413],{},[1298,5348,5349,5355,5359,5362],{},[1322,5350,5351,5352,5354],{},"Enrolled device, ",[957,5353,4662],{}," wired",[1322,5356,5357],{},[957,5358,4787],{},[1322,5360,5361],{},"recognizes the revealed echo",[1322,5363,5364],{},"Full mutual authentication; someone holding a copy of the keyring file cannot read the echo out of it.",[1298,5366,5367,5373,5377,5379],{},[1322,5368,5369,5370,5372],{},"Live keyring enrolled without ",[957,5371,4662],{},", or a pod recipient (default)",[1322,5374,5375],{},[957,5376,4774],{},[1322,5378,5361],{},[1322,5380,5381],{},"Full mutual authentication against anyone who does not hold the file. A file-holder who also knows the prompt can decrypt the blob.",[1298,5383,5384,5387,5391,5394],{},[1322,5385,5386],{},"Sealed keyring opened on a foreign device (no provider, or a different provider id)",[1322,5388,5389],{},[957,5390,4787],{},[1322,5392,5393],{},"types the echo",[1322,5395,5396],{},"The three-step structure and a verified echo; no reveal, so no mutual authentication.",[1298,5398,5399,5404,5408,5410],{},[1322,5400,5401,5402],{},"Pod recipient the owner enrolled as ",[957,5403,4819],{},[1322,5405,5406],{},[957,5407,4810],{},[1322,5409,5393],{},[1322,5411,5412],{},"Same as above.",[1298,5414,5415,5418,5421,5424],{},[1322,5416,5417],{},"A player that only offers single-field secret entry",[1322,5419,5420],{},"any",[1322,5422,5423],{},"nothing — it cannot proceed",[1322,5425,5426,5429],{},[957,5427,5428],{},"EchoCeremonyRequiredError",". There is no downgrade to a plain phrase.",[1573,5431,5433],{"id":5432},"the-conforming-player-contract","The conforming-player contract",[940,5435,5436,5437,5440],{},"Anything that opens an echo keyring — your app, a generic viewer, a future universal player — is a ",[943,5438,5439],{},"player",", and the ceremony is a property of the data it opens, not of the app. The kernel enforces what an API can enforce; the rest is normative and stated here so that \"conforming\" has a meaning:",[1011,5442,5443,5457,5480,5494],{},[1014,5444,5445,5446,5449,5450,5452,5453,5456],{},"A player ",[943,5447,5448],{},"MUST NOT"," present a single-field secret entry for an echo keyring, and ",[943,5451,5448],{}," offer a \"paste your full secret\" escape hatch. None can work: the KDF input is a length-prefixed encoding of the three parts that begins with a byte ",[957,5454,5455],{},"TextEncoder"," can never emit, so no string's encoding can equal it, and the key-derivation dispatch additionally refuses a string outright on an echo keyring.",[1014,5458,5459,5460,5462,5463,5466,5467,5470,5471,5473,5474,5476,5477,5479],{},"When the user does ",[943,5461,5307],{}," recognize the revealed echo, the player ",[943,5464,5465],{},"MUST"," treat the ceremony as dead and show a hard warning: ",[948,5468,5469],{},"stop, do not continue, open the official app, rotate the secret now",". It ",[943,5472,5448],{}," offer a \"skip verification\" or \"verification unavailable\" affordance, and ",[943,5475,5465],{}," abandon the ceremony without calling ",[957,5478,5286],{},". The kernel cannot enforce this terminal state — it is the player's obligation.",[1014,5481,5482,5483,5486,5487,5489,5490,1696],{},"Rate limiting is also the player's obligation: the kernel keeps ",[943,5484,5485],{},"no attempt counter"," on the prompt. Wrong prompts return ",[957,5488,5118],{}," indefinitely; lockout, backoff, and duress handling belong to the app (see ",[966,5491,5492],{"href":330},[957,5493,1458],{},[1014,5495,5445,5496,5499,5500,5503,5504,5507],{},[943,5497,5498],{},"SHOULD"," mask the echo on screen when ",[957,5501,5502],{},"ceremony.maskHint"," is present, and ",[943,5505,5506],{},"MAY"," mask it by its own rules when it is absent — the reveal is on-screen material, so it is shoulder-surfable.",[1573,5509,5511],{"id":5510},"validation-per-part-floors","Validation — per-part floors",[940,5513,5514],{},"Echo secrets are natural-language sentences, so their floors differ from the standard phrase's:",[1292,5516,5517,5528],{},[1295,5518,5519],{},[1298,5520,5521,5524,5526],{},[1301,5522,5523],{},"Check",[1301,5525,1592],{},[1301,5527,1595],{},[1317,5529,5530,5540,5554,5564,5581],{},[1298,5531,5532,5535,5538],{},[1322,5533,5534],{},"Each of the three parts",[1322,5536,5537],{},"non-empty after trimming",[1322,5539,1622],{},[1298,5541,5542,5545,5548],{},[1322,5543,5544],{},"Prompt",[1322,5546,5547],{},"minimum word count",[1322,5549,5550,5551,2614],{},"3 words (",[957,5552,5553],{},"DEFAULT_ECHO_PROMPT_MIN_WORDS",[1298,5555,5556,5559,5561],{},[1322,5557,5558],{},"Prompt + echo + key, joined by single spaces",[1322,5560,5547],{},[1322,5562,5563],{},"6 words — the standard whole-secret floor",[1298,5565,5566,5569,5572],{},[1322,5567,5568],{},"Every word, in both checks",[1322,5570,5571],{},"minimum length",[1322,5573,5574,1824,5577,5580],{},[943,5575,5576],{},"1 character",[957,5578,5579],{},"DEFAULT_ECHO_MIN_WORD_LENGTH","), relaxed from the standard 3",[1298,5582,5583,5586,5589],{},[1322,5584,5585],{},"Every part",[1322,5587,5588],{},"character pattern",[1322,5590,5591,5592],{},"lowercase letters and single spaces, as in ",[966,5593,1576],{"href":5594},"#phrase-format",[940,5596,5597,5598,5600,5601,5604,5605,968],{},"The relaxed word-length floor is what lets natural sentences validate: ",[957,5599,4524],{}," is three real words, two of them shorter than the standard 3-character floor. The character pattern is unchanged, so punctuation still has to go — write ",[957,5602,5603],{},"santanna",", not ",[957,5606,5607],{},"sant.anna",[1049,5609,5611],{"className":1051,"code":5610,"language":1053,"meta":1054,"style":1054},"import { validateEchoSecret, assertStrongEchoSecret } from '@noy-db\u002Fhub'\n\nvalidateEchoSecret({\n  prompt: 'mi chiamo vicio',\n  echo: 'ma da piccolo al santanna mi chiamavano',\n  key: 'ciccio',\n})\n\u002F\u002F → { ok: true, words: 11 }\n\nvalidateEchoSecret({ prompt: 'ciao', echo: 'come stai oggi amico', key: 'bene grazie' })\n\u002F\u002F → { ok: false, reason: 'too-few-words', minimum: 3, got: 1 }   — the PROMPT floor\n",[957,5612,5613,5637,5641,5650,5664,5679,5694,5700,5705,5709,5759],{"__ignoreMap":1054},[1058,5614,5615,5617,5619,5622,5624,5627,5629,5631,5633,5635],{"class":1060,"line":1061},[1058,5616,1065],{"class":1064},[1058,5618,1069],{"class":1068},[1058,5620,5621],{"class":1072}," validateEchoSecret",[1058,5623,1133],{"class":1068},[1058,5625,5626],{"class":1072}," assertStrongEchoSecret",[1058,5628,1076],{"class":1068},[1058,5630,1079],{"class":1064},[1058,5632,1082],{"class":1068},[1058,5634,992],{"class":1085},[1058,5636,1088],{"class":1068},[1058,5638,5639],{"class":1060,"line":1091},[1058,5640,1095],{"emptyLinePlaceholder":1094},[1058,5642,5643,5646,5648],{"class":1060,"line":1098},[1058,5644,5645],{"class":1121},"validateEchoSecret",[1058,5647,1124],{"class":1072},[1058,5649,3441],{"class":1068},[1058,5651,5652,5654,5656,5658,5660,5662],{"class":1060,"line":1105},[1058,5653,5064],{"class":1218},[1058,5655,1167],{"class":1068},[1058,5657,1082],{"class":1068},[1058,5659,4524],{"class":1085},[1058,5661,1229],{"class":1068},[1058,5663,2377],{"class":1068},[1058,5665,5666,5669,5671,5673,5675,5677],{"class":1060,"line":1762},[1058,5667,5668],{"class":1218},"  echo",[1058,5670,1167],{"class":1068},[1058,5672,1082],{"class":1068},[1058,5674,4540],{"class":1085},[1058,5676,1229],{"class":1068},[1058,5678,2377],{"class":1068},[1058,5680,5681,5684,5686,5688,5690,5692],{"class":1060,"line":1767},[1058,5682,5683],{"class":1218},"  key",[1058,5685,1167],{"class":1068},[1058,5687,1082],{"class":1068},[1058,5689,4556],{"class":1085},[1058,5691,1229],{"class":1068},[1058,5693,2377],{"class":1068},[1058,5695,5696,5698],{"class":1060,"line":1783},[1058,5697,1139],{"class":1068},[1058,5699,1142],{"class":1072},[1058,5701,5702],{"class":1060,"line":1789},[1058,5703,5704],{"class":1101},"\u002F\u002F → { ok: true, words: 11 }\n",[1058,5706,5707],{"class":1060,"line":1794},[1058,5708,1095],{"emptyLinePlaceholder":1094},[1058,5710,5711,5713,5715,5717,5720,5722,5724,5727,5729,5731,5733,5735,5737,5740,5742,5744,5746,5748,5750,5753,5755,5757],{"class":1060,"line":1810},[1058,5712,5645],{"class":1121},[1058,5714,1124],{"class":1072},[1058,5716,1127],{"class":1068},[1058,5718,5719],{"class":1218}," prompt",[1058,5721,1167],{"class":1068},[1058,5723,1082],{"class":1068},[1058,5725,5726],{"class":1085},"ciao",[1058,5728,1229],{"class":1068},[1058,5730,1133],{"class":1068},[1058,5732,5207],{"class":1218},[1058,5734,1167],{"class":1068},[1058,5736,1082],{"class":1068},[1058,5738,5739],{"class":1085},"come stai oggi amico",[1058,5741,1229],{"class":1068},[1058,5743,1133],{"class":1068},[1058,5745,5174],{"class":1218},[1058,5747,1167],{"class":1068},[1058,5749,1082],{"class":1068},[1058,5751,5752],{"class":1085},"bene grazie",[1058,5754,1229],{"class":1068},[1058,5756,1076],{"class":1068},[1058,5758,1142],{"class":1072},[1058,5760,5761],{"class":1060,"line":2103},[1058,5762,5763],{"class":1101},"\u002F\u002F → { ok: false, reason: 'too-few-words', minimum: 3, got: 1 }   — the PROMPT floor\n",[940,5765,5766,5767,5770,5771,5774,5775,5779,5780,5783],{},"The prompt gets its own floor because it is the part an attacker can attack ",[948,5768,5769],{},"in isolation",": a guessed prompt yields the echo, which is exactly what a convincing phish would need. The ",[943,5772,5773],{},"echo has no dedicated floor"," — it is free-form and never typed on the normal path — but it is not free of consequence either: it is grindable offline by anyone holding the keyring file, so longer is better. See the ",[966,5776,5778],{"href":5777},"\u002Ffaq\u002Fthreat-model#a-phishing-page-imitating-the-unlock-screen","threat model"," for what that means, and the ",[966,5781,5782],{"href":540},"echo-secret enrollment recipe"," for how to choose one.",[940,5785,5786,5787,5790,5791,1167],{},"Override the floors per vault with ",[957,5788,5789],{},"echoSecretPolicy: { prompt?: SecretPolicy, combined?: SecretPolicy }"," — the parts-path counterpart of ",[957,5792,5793],{},"secretPolicy",[1049,5795,5797],{"className":1051,"code":5796,"language":1053,"meta":1054,"style":1054},"await createNoydb({\n  store, user: 'vicio', secretMode: 'echo', secret: parts,\n  validateSecret: true,\n  echoSecretPolicy: { prompt: { minWords: 4 } },\n})\n",[957,5798,5799,5809,5847,5857,5884],{"__ignoreMap":1054},[1058,5800,5801,5803,5805,5807],{"class":1060,"line":1061},[1058,5802,1260],{"class":1064},[1058,5804,1073],{"class":1121},[1058,5806,1124],{"class":1072},[1058,5808,3441],{"class":1068},[1058,5810,5811,5813,5815,5817,5819,5821,5823,5825,5827,5829,5831,5833,5835,5837,5839,5841,5843,5845],{"class":1060,"line":1091},[1058,5812,3446],{"class":1072},[1058,5814,1133],{"class":1068},[1058,5816,4700],{"class":1218},[1058,5818,1167],{"class":1068},[1058,5820,1082],{"class":1068},[1058,5822,4485],{"class":1085},[1058,5824,1229],{"class":1068},[1058,5826,1133],{"class":1068},[1058,5828,4713],{"class":1218},[1058,5830,1167],{"class":1068},[1058,5832,1082],{"class":1068},[1058,5834,1532],{"class":1085},[1058,5836,1229],{"class":1068},[1058,5838,1133],{"class":1068},[1058,5840,4726],{"class":1218},[1058,5842,1167],{"class":1068},[1058,5844,4904],{"class":1072},[1058,5846,2377],{"class":1068},[1058,5848,5849,5851,5853,5855],{"class":1060,"line":1098},[1058,5850,4588],{"class":1218},[1058,5852,1167],{"class":1068},[1058,5854,1902],{"class":1901},[1058,5856,2377],{"class":1068},[1058,5858,5859,5862,5864,5866,5868,5870,5872,5875,5877,5880,5882],{"class":1060,"line":1105},[1058,5860,5861],{"class":1218},"  echoSecretPolicy",[1058,5863,1167],{"class":1068},[1058,5865,1069],{"class":1068},[1058,5867,5719],{"class":1218},[1058,5869,1167],{"class":1068},[1058,5871,1069],{"class":1068},[1058,5873,5874],{"class":1218}," minWords",[1058,5876,1167],{"class":1068},[1058,5878,5879],{"class":1239}," 4",[1058,5881,1076],{"class":1068},[1058,5883,4247],{"class":1068},[1058,5885,5886,5888],{"class":1060,"line":1762},[1058,5887,1139],{"class":1068},[1058,5889,1142],{"class":1072},[940,5891,5892,5893,5895,5896,1380,5898,5900,5901,1696],{},"Enforcement follows the same rule as standard mode: opt-in at vault creation (",[957,5894,1837],{},"), on by default for ",[957,5897,3837],{},[957,5899,4177],{}," (bypass with ",[957,5902,5903],{},"allowWeakSecret: true",[1573,5905,5907],{"id":5906},"rotation-and-migration","Rotation and migration",[940,5909,5910,5911,5918,5919,5921],{},"The ",[943,5912,5913,5914,5917],{},"shape of ",[957,5915,5916],{},"newSecret"," decides the mode after rotation"," — a struct enrolls (or re-mints) an echo block, a string removes it. That makes ",[957,5920,3837],{}," the migration path in both directions:",[1049,5923,5925],{"className":1051,"code":5924,"language":1053,"meta":1054,"style":1054},"\u002F\u002F standard → echo (the upgrade)\nawait db.team.rotateSecret('acme', {\n  oldSecret: 'correct horse battery staple printer toaster',\n  newSecret: { prompt: 'mi chiamo vicio', echo: 'ma da piccolo al santanna mi chiamavano', key: 'ciccio' },\n  echoOptions: { deviceSeal, maskHint: 'first-letters' },   \u002F\u002F omit deviceSeal ⇒ reveal: 'portable'\n})\n\n\u002F\u002F echo → echo (re-mint: fresh verifiers, fresh reveal blob)\nawait db.team.rotateSecret('acme', { oldSecret: oldParts, newSecret: newParts })\n\n\u002F\u002F echo → standard (the downgrade) — refused unless you say so\nawait db.team.rotateSecret('acme', {\n  oldSecret: oldParts,\n  newSecret: 'glasses cabinet bicycle umbrella thunder velvet',\n  allowModeDowngrade: true,\n})\n",[957,5926,5927,5932,5958,5972,6016,6046,6052,6056,6061,6108,6112,6117,6143,6153,6167,6178],{"__ignoreMap":1054},[1058,5928,5929],{"class":1060,"line":1061},[1058,5930,5931],{"class":1101},"\u002F\u002F standard → echo (the upgrade)\n",[1058,5933,5934,5936,5938,5940,5942,5944,5946,5948,5950,5952,5954,5956],{"class":1060,"line":1091},[1058,5935,1260],{"class":1064},[1058,5937,1263],{"class":1072},[1058,5939,968],{"class":1068},[1058,5941,868],{"class":1072},[1058,5943,968],{"class":1068},[1058,5945,3837],{"class":1121},[1058,5947,1124],{"class":1072},[1058,5949,1229],{"class":1068},[1058,5951,1279],{"class":1085},[1058,5953,1229],{"class":1068},[1058,5955,1133],{"class":1068},[1058,5957,1968],{"class":1068},[1058,5959,5960,5962,5964,5966,5968,5970],{"class":1060,"line":1098},[1058,5961,3854],{"class":1218},[1058,5963,1167],{"class":1068},[1058,5965,1082],{"class":1068},[1058,5967,1750],{"class":1085},[1058,5969,1229],{"class":1068},[1058,5971,2377],{"class":1068},[1058,5973,5974,5976,5978,5980,5982,5984,5986,5988,5990,5992,5994,5996,5998,6000,6002,6004,6006,6008,6010,6012,6014],{"class":1060,"line":1105},[1058,5975,3869],{"class":1218},[1058,5977,1167],{"class":1068},[1058,5979,1069],{"class":1068},[1058,5981,5719],{"class":1218},[1058,5983,1167],{"class":1068},[1058,5985,1082],{"class":1068},[1058,5987,4524],{"class":1085},[1058,5989,1229],{"class":1068},[1058,5991,1133],{"class":1068},[1058,5993,5207],{"class":1218},[1058,5995,1167],{"class":1068},[1058,5997,1082],{"class":1068},[1058,5999,4540],{"class":1085},[1058,6001,1229],{"class":1068},[1058,6003,1133],{"class":1068},[1058,6005,5174],{"class":1218},[1058,6007,1167],{"class":1068},[1058,6009,1082],{"class":1068},[1058,6011,4556],{"class":1085},[1058,6013,1229],{"class":1068},[1058,6015,4247],{"class":1068},[1058,6017,6018,6021,6023,6025,6028,6030,6033,6035,6037,6039,6041,6043],{"class":1060,"line":1762},[1058,6019,6020],{"class":1218},"  echoOptions",[1058,6022,1167],{"class":1068},[1058,6024,1069],{"class":1068},[1058,6026,6027],{"class":1072}," deviceSeal",[1058,6029,1133],{"class":1068},[1058,6031,6032],{"class":1218}," maskHint",[1058,6034,1167],{"class":1068},[1058,6036,1082],{"class":1068},[1058,6038,4576],{"class":1085},[1058,6040,1229],{"class":1068},[1058,6042,3676],{"class":1068},[1058,6044,6045],{"class":1101},"   \u002F\u002F omit deviceSeal ⇒ reveal: 'portable'\n",[1058,6047,6048,6050],{"class":1060,"line":1767},[1058,6049,1139],{"class":1068},[1058,6051,1142],{"class":1072},[1058,6053,6054],{"class":1060,"line":1783},[1058,6055,1095],{"emptyLinePlaceholder":1094},[1058,6057,6058],{"class":1060,"line":1789},[1058,6059,6060],{"class":1101},"\u002F\u002F echo → echo (re-mint: fresh verifiers, fresh reveal blob)\n",[1058,6062,6063,6065,6067,6069,6071,6073,6075,6077,6079,6081,6083,6085,6087,6090,6092,6095,6097,6099,6101,6104,6106],{"class":1060,"line":1794},[1058,6064,1260],{"class":1064},[1058,6066,1263],{"class":1072},[1058,6068,968],{"class":1068},[1058,6070,868],{"class":1072},[1058,6072,968],{"class":1068},[1058,6074,3837],{"class":1121},[1058,6076,1124],{"class":1072},[1058,6078,1229],{"class":1068},[1058,6080,1279],{"class":1085},[1058,6082,1229],{"class":1068},[1058,6084,1133],{"class":1068},[1058,6086,1069],{"class":1068},[1058,6088,6089],{"class":1218}," oldSecret",[1058,6091,1167],{"class":1068},[1058,6093,6094],{"class":1072}," oldParts",[1058,6096,1133],{"class":1068},[1058,6098,4000],{"class":1218},[1058,6100,1167],{"class":1068},[1058,6102,6103],{"class":1072}," newParts ",[1058,6105,1139],{"class":1068},[1058,6107,1142],{"class":1072},[1058,6109,6110],{"class":1060,"line":1810},[1058,6111,1095],{"emptyLinePlaceholder":1094},[1058,6113,6114],{"class":1060,"line":2103},[1058,6115,6116],{"class":1101},"\u002F\u002F echo → standard (the downgrade) — refused unless you say so\n",[1058,6118,6119,6121,6123,6125,6127,6129,6131,6133,6135,6137,6139,6141],{"class":1060,"line":2119},[1058,6120,1260],{"class":1064},[1058,6122,1263],{"class":1072},[1058,6124,968],{"class":1068},[1058,6126,868],{"class":1072},[1058,6128,968],{"class":1068},[1058,6130,3837],{"class":1121},[1058,6132,1124],{"class":1072},[1058,6134,1229],{"class":1068},[1058,6136,1279],{"class":1085},[1058,6138,1229],{"class":1068},[1058,6140,1133],{"class":1068},[1058,6142,1968],{"class":1068},[1058,6144,6145,6147,6149,6151],{"class":1060,"line":2130},[1058,6146,3854],{"class":1218},[1058,6148,1167],{"class":1068},[1058,6150,6094],{"class":1072},[1058,6152,2377],{"class":1068},[1058,6154,6155,6157,6159,6161,6163,6165],{"class":1060,"line":2135},[1058,6156,3869],{"class":1218},[1058,6158,1167],{"class":1068},[1058,6160,1082],{"class":1068},[1058,6162,3876],{"class":1085},[1058,6164,1229],{"class":1068},[1058,6166,2377],{"class":1068},[1058,6168,6169,6172,6174,6176],{"class":1060,"line":2140},[1058,6170,6171],{"class":1218},"  allowModeDowngrade",[1058,6173,1167],{"class":1068},[1058,6175,1902],{"class":1901},[1058,6177,2377],{"class":1068},[1058,6179,6180,6182],{"class":1060,"line":2146},[1058,6181,1139],{"class":1068},[1058,6183,1142],{"class":1072},[940,6185,6186,6187,6190,6191,4357,6193,6195,6196,6199,6200,6202,6203,6206,6207,6209,6210,6212,6213,968],{},"Without ",[957,6188,6189],{},"allowModeDowngrade: true",", an echo keyring plus a string ",[957,6192,5916],{},[957,6194,4651],{}," ",[943,6197,6198],{},"before anything is written"," — losing the anti-phishing ceremony should never be the accidental consequence of an argument shape. ",[957,6201,3945],{}," must match the ",[948,6204,6205],{},"stored"," mode: a string presented to an echo keyring throws ",[957,6208,5428],{},", a struct presented to a standard one throws ",[957,6211,4651],{},". Tier-2 slots survive an echo rotation exactly as elsewhere — via ",[957,6214,3959],{},[940,6216,6217,6218,6221],{},"Two reset paths convert echo → standard ",[943,6219,6220],{},"without"," the flag, by design, because both are already proof-gated ceremonies rather than a routine change:",[1011,6223,6224,6244],{},[1014,6225,6226,6231,6232,6237,6238,6241,6242,1696],{},[943,6227,6228],{},[957,6229,6230],{},"db.team.recoverSecret"," — the caller has presented a recovery code or Shamir shares. It accepts either shape; a struct enrolls a fresh echo block that is ",[943,6233,6234,6235],{},"always ",[957,6236,4774],{}," (there is no ",[957,6239,6240],{},"echoOptions"," here: the device running a recovery is not necessarily the device that will unlock later — seal it afterwards with an explicit ",[957,6243,3837],{},[1014,6245,6246,6251,6252,6254,6255,6257],{},[943,6247,6248],{},[957,6249,6250],{},"db.team.recoverUser"," (peer recovery) — an issuer re-wraps someone else's keyring under a temporary ",[943,6253,2057],{}," secret, so the target always lands on a standard keyring and its echo block is dropped. Peer recovery is a single-use bridge; the recipient picks the echo shape back up with their own ",[957,6256,3837],{}," on first session.",[1573,6259,6261],{"id":6260},"pod-recipients-the-ceremony-travels-with-the-data","Pod recipients — the ceremony travels with the data",[940,6263,6264,6265,6195,6267,6195,6270,6273],{},"A recipient slot inside a ",[957,6266,3265],{},[966,6268,6269],{"href":630},"pod",[948,6271,6272],{},"is"," a keyring, so an echo recipient carries the ceremony to whoever opens the pod — including a pod that has outlived the app that wrote it:",[1049,6275,6277],{"className":1051,"code":6276,"language":1053,"meta":1054,"style":1054},"import { writePod } from '@noy-db\u002Fhub\u002Fpod'\n\nconst bytes = await writePod(vault, {\n  recipients: [\n    { id: 'auditor', role: 'viewer', secret: { prompt, echo, key } },                   \u002F\u002F reveal: 'portable'\n    { id: 'notary',  role: 'viewer', secret: { prompt, echo, key, reveal: 'none' } },   \u002F\u002F verified typed echo\n  ],\n})\n",[957,6278,6279,6299,6303,6323,6332,6387,6453,6459],{"__ignoreMap":1054},[1058,6280,6281,6283,6285,6288,6290,6292,6294,6297],{"class":1060,"line":1061},[1058,6282,1065],{"class":1064},[1058,6284,1069],{"class":1068},[1058,6286,6287],{"class":1072}," writePod",[1058,6289,1076],{"class":1068},[1058,6291,1079],{"class":1064},[1058,6293,1082],{"class":1068},[1058,6295,6296],{"class":1085},"@noy-db\u002Fhub\u002Fpod",[1058,6298,1088],{"class":1068},[1058,6300,6301],{"class":1060,"line":1091},[1058,6302,1095],{"emptyLinePlaceholder":1094},[1058,6304,6305,6307,6310,6312,6314,6316,6319,6321],{"class":1060,"line":1098},[1058,6306,1109],{"class":1108},[1058,6308,6309],{"class":1072}," bytes ",[1058,6311,1115],{"class":1068},[1058,6313,1118],{"class":1064},[1058,6315,6287],{"class":1121},[1058,6317,6318],{"class":1072},"(vault",[1058,6320,1133],{"class":1068},[1058,6322,1968],{"class":1068},[1058,6324,6325,6328,6330],{"class":1060,"line":1105},[1058,6326,6327],{"class":1218},"  recipients",[1058,6329,1167],{"class":1068},[1058,6331,4300],{"class":1072},[1058,6333,6334,6336,6338,6340,6342,6345,6347,6349,6352,6354,6356,6359,6361,6363,6365,6367,6369,6371,6373,6375,6377,6380,6382,6384],{"class":1060,"line":1762},[1058,6335,4305],{"class":1068},[1058,6337,4924],{"class":1218},[1058,6339,1167],{"class":1068},[1058,6341,1082],{"class":1068},[1058,6343,6344],{"class":1085},"auditor",[1058,6346,1229],{"class":1068},[1058,6348,1133],{"class":1068},[1058,6350,6351],{"class":1218}," role",[1058,6353,1167],{"class":1068},[1058,6355,1082],{"class":1068},[1058,6357,6358],{"class":1085},"viewer",[1058,6360,1229],{"class":1068},[1058,6362,1133],{"class":1068},[1058,6364,4726],{"class":1218},[1058,6366,1167],{"class":1068},[1058,6368,1069],{"class":1068},[1058,6370,5719],{"class":1072},[1058,6372,1133],{"class":1068},[1058,6374,5207],{"class":1072},[1058,6376,1133],{"class":1068},[1058,6378,6379],{"class":1072}," key ",[1058,6381,1139],{"class":1068},[1058,6383,3676],{"class":1068},[1058,6385,6386],{"class":1101},"                   \u002F\u002F reveal: 'portable'\n",[1058,6388,6389,6391,6393,6395,6397,6400,6402,6404,6407,6409,6411,6413,6415,6417,6419,6421,6423,6425,6427,6429,6431,6433,6435,6438,6440,6442,6444,6446,6448,6450],{"class":1060,"line":1767},[1058,6390,4305],{"class":1068},[1058,6392,4924],{"class":1218},[1058,6394,1167],{"class":1068},[1058,6396,1082],{"class":1068},[1058,6398,6399],{"class":1085},"notary",[1058,6401,1229],{"class":1068},[1058,6403,1133],{"class":1068},[1058,6405,6406],{"class":1218},"  role",[1058,6408,1167],{"class":1068},[1058,6410,1082],{"class":1068},[1058,6412,6358],{"class":1085},[1058,6414,1229],{"class":1068},[1058,6416,1133],{"class":1068},[1058,6418,4726],{"class":1218},[1058,6420,1167],{"class":1068},[1058,6422,1069],{"class":1068},[1058,6424,5719],{"class":1072},[1058,6426,1133],{"class":1068},[1058,6428,5207],{"class":1072},[1058,6430,1133],{"class":1068},[1058,6432,5174],{"class":1072},[1058,6434,1133],{"class":1068},[1058,6436,6437],{"class":1218}," reveal",[1058,6439,1167],{"class":1068},[1058,6441,1082],{"class":1068},[1058,6443,4810],{"class":1085},[1058,6445,1229],{"class":1068},[1058,6447,1076],{"class":1068},[1058,6449,3676],{"class":1068},[1058,6451,6452],{"class":1101},"   \u002F\u002F verified typed echo\n",[1058,6454,6455,6457],{"class":1060,"line":1783},[1058,6456,4337],{"class":1072},[1058,6458,2377],{"class":1068},[1058,6460,6461,6463],{"class":1060,"line":1789},[1058,6462,1139],{"class":1068},[1058,6464,1142],{"class":1072},[940,6466,6467,6470,6471,6474,6475,4956,6478,6481,6482,4956,6485,6491],{},[957,6468,6469],{},"PodRecipient.secret"," widens to ",[957,6472,6473],{},"string | EchoRecipientSecret",", where ",[957,6476,6477],{},"EchoRecipientSecret",[957,6479,6480],{},"{ prompt, echo, key, reveal?: 'portable' | 'none' }",". ",[957,6483,6484],{},"'sealed'",[943,6486,6487,6488],{},"not reachable from ",[957,6489,6490],{},"writePod"," — sealing needs a device to seal against, and a pod being written has none.",[940,6493,6494],{},"On the other end, a player that restores the pod's keyrings runs the same ceremony against the restored row:",[1049,6496,6498],{"className":1051,"code":6497,"language":1053,"meta":1054,"style":1054},"const ceremony = await beginEchoUnlock(restoredStore, compartment, { userId: 'auditor', prompt })\nconst unlocked = await ceremony.complete({ key })\n",[957,6499,6500,6544],{"__ignoreMap":1054},[1058,6501,6502,6504,6506,6508,6510,6512,6515,6517,6520,6522,6524,6527,6529,6531,6533,6535,6537,6540,6542],{"class":1060,"line":1061},[1058,6503,1109],{"class":1108},[1058,6505,5023],{"class":1072},[1058,6507,1115],{"class":1068},[1058,6509,1118],{"class":1064},[1058,6511,4992],{"class":1121},[1058,6513,6514],{"class":1072},"(restoredStore",[1058,6516,1133],{"class":1068},[1058,6518,6519],{"class":1072}," compartment",[1058,6521,1133],{"class":1068},[1058,6523,1069],{"class":1068},[1058,6525,6526],{"class":1218}," userId",[1058,6528,1167],{"class":1068},[1058,6530,1082],{"class":1068},[1058,6532,6344],{"class":1085},[1058,6534,1229],{"class":1068},[1058,6536,1133],{"class":1068},[1058,6538,6539],{"class":1072}," prompt ",[1058,6541,1139],{"class":1068},[1058,6543,1142],{"class":1072},[1058,6545,6546,6548,6550,6552,6554,6556,6558,6560,6562,6564,6566,6568],{"class":1060,"line":1091},[1058,6547,1109],{"class":1108},[1058,6549,2569],{"class":1072},[1058,6551,1115],{"class":1068},[1058,6553,1118],{"class":1064},[1058,6555,5142],{"class":1072},[1058,6557,968],{"class":1068},[1058,6559,5167],{"class":1121},[1058,6561,1124],{"class":1072},[1058,6563,1127],{"class":1068},[1058,6565,6379],{"class":1072},[1058,6567,1139],{"class":1068},[1058,6569,1142],{"class":1072},[1573,6571,6573],{"id":6572},"errors","Errors",[1292,6575,6576,6589],{},[1295,6577,6578],{},[1298,6579,6580,6583,6586],{},[1301,6581,6582],{},"Error",[1301,6584,6585],{},"Code",[1301,6587,6588],{},"Thrown when",[1317,6590,6591,6615,6632,6646,6673],{},[1298,6592,6593,6597,6602],{},[1322,6594,6595],{},[957,6596,5428],{},[1322,6598,6599],{},[957,6600,6601],{},"ECHO_CEREMONY_REQUIRED",[1322,6603,6604,6605,6608,6609,6611,6612,6614],{},"A single string was presented to an echo keyring — ",[957,6606,6607],{},"createNoydb({ secret: '…' })",", or a string ",[957,6610,3945],{}," on ",[957,6613,3837],{},". The message names the ceremony; there is no fallback.",[1298,6616,6617,6621,6626],{},[1322,6618,6619],{},[957,6620,5118],{},[1322,6622,6623],{},[957,6624,6625],{},"WRONG_PROMPT",[1322,6627,6628,6629,6631],{},"The typed prompt failed its verifier, or could not open a ",[957,6630,4774],{}," reveal blob. Nothing is revealed.",[1298,6633,6634,6638,6643],{},[1322,6635,6636],{},[957,6637,5301],{},[1322,6639,6640],{},[957,6641,6642],{},"WRONG_ECHO",[1322,6644,6645],{},"Degraded path: the typed echo failed its verifier — or a typed echo was supplied that disagrees with an already-resolved reveal.",[1298,6647,6648,6652,6655],{},[1322,6649,6650],{},[957,6651,4651],{},[1322,6653,6654],{},"—",[1322,6656,6657,6658,6660,6661,6663,6664,6666,6667,6669,6670,968],{},"Mode\u002Fshape disagreements at ",[957,6659,1827],{},"; ",[957,6662,5104],{}," on a keyring with no echo block; ",[957,6665,5286],{}," without an ",[957,6668,1532],{}," on a degraded ceremony; an echo → standard rotation without ",[957,6671,6672],{},"allowModeDowngrade",[1298,6674,6675,6680,6682],{},[1322,6676,6677],{},[957,6678,6679],{},"WeakSecretError",[1322,6681,6654],{},[1322,6683,6684,6685,1158,6688,6691],{},"A part fails its floor when validation is on; ",[957,6686,6687],{},"reason",[957,6689,6690],{},"suggestion"," say which.",[1573,6693,463],{"id":6694},"threat-model",[940,6696,6697,6698,6701,6702,6704],{},"Summarized in ",[966,6699,6700],{"href":5777},"Threat model — a phishing page imitating the unlock screen",", including what echo mode does ",[948,6703,5307],{}," defend: the keyring's prompt and echo verifiers are offline brute-force oracles for anyone holding the file, and a sealed reveal blob discloses the echo's byte length. Nothing protects a user who types a secret into genuinely hostile code — echo mode targets remote phishing pages and honest-but-lazy players, not a malicious player the user hands the pod to.",[1468,6706,6708],{"id":6707},"managed-secret-mode","Managed-secret mode",[940,6710,6711],{},"Selected at vault creation, this mode makes the vault resistant to coercion: the hub — not the user — generates the secret, so there is nothing for the user to reveal.",[1049,6713,6715],{"className":1051,"code":6714,"language":1053,"meta":1054,"style":1054},"import { createNoydb, MemorySealer } from '@noy-db\u002Fhub'\n\nconst db = await createNoydb({\n  store,\n  secretMode: 'managed',\n  sealingKey: new MemorySealer({ id: 'dev-test' }), \u002F\u002F swap for a real per-platform provider in production\n})\n\n\u002F\u002F Managed mode requires at least one strong (Shamir) recovery profile —\n\u002F\u002F paper alone has no user-known secret to fall back on.\nconst { vault, recoveryEnrollments } = await db.team.openVaultAndEnrollRecovery('acme', {\n  recovery: [{ profile: 'shamir', k: 2, n: 3 }],\n})\n\u002F\u002F → recoveryEnrollments[0].shares — show once, the hub does not retain them\n",[957,6716,6717,6740,6744,6760,6766,6780,6815,6821,6825,6830,6835,6878,6922,6928],{"__ignoreMap":1054},[1058,6718,6719,6721,6723,6725,6727,6730,6732,6734,6736,6738],{"class":1060,"line":1061},[1058,6720,1065],{"class":1064},[1058,6722,1069],{"class":1068},[1058,6724,1073],{"class":1072},[1058,6726,1133],{"class":1068},[1058,6728,6729],{"class":1072}," MemorySealer",[1058,6731,1076],{"class":1068},[1058,6733,1079],{"class":1064},[1058,6735,1082],{"class":1068},[1058,6737,992],{"class":1085},[1058,6739,1088],{"class":1068},[1058,6741,6742],{"class":1060,"line":1091},[1058,6743,1095],{"emptyLinePlaceholder":1094},[1058,6745,6746,6748,6750,6752,6754,6756,6758],{"class":1060,"line":1098},[1058,6747,1109],{"class":1108},[1058,6749,1112],{"class":1072},[1058,6751,1115],{"class":1068},[1058,6753,1118],{"class":1064},[1058,6755,1073],{"class":1121},[1058,6757,1124],{"class":1072},[1058,6759,3441],{"class":1068},[1058,6761,6762,6764],{"class":1060,"line":1105},[1058,6763,3446],{"class":1072},[1058,6765,2377],{"class":1068},[1058,6767,6768,6770,6772,6774,6776,6778],{"class":1060,"line":1762},[1058,6769,4494],{"class":1218},[1058,6771,1167],{"class":1068},[1058,6773,1082],{"class":1068},[1058,6775,1550],{"class":1085},[1058,6777,1229],{"class":1068},[1058,6779,2377],{"class":1068},[1058,6781,6782,6785,6787,6789,6791,6793,6795,6797,6799,6801,6804,6806,6808,6810,6812],{"class":1060,"line":1767},[1058,6783,6784],{"class":1218},"  sealingKey",[1058,6786,1167],{"class":1068},[1058,6788,3684],{"class":1068},[1058,6790,6729],{"class":1121},[1058,6792,1124],{"class":1072},[1058,6794,1127],{"class":1068},[1058,6796,4924],{"class":1218},[1058,6798,1167],{"class":1068},[1058,6800,1082],{"class":1068},[1058,6802,6803],{"class":1085},"dev-test",[1058,6805,1229],{"class":1068},[1058,6807,1076],{"class":1068},[1058,6809,2614],{"class":1072},[1058,6811,1133],{"class":1068},[1058,6813,6814],{"class":1101}," \u002F\u002F swap for a real per-platform provider in production\n",[1058,6816,6817,6819],{"class":1060,"line":1783},[1058,6818,1139],{"class":1068},[1058,6820,1142],{"class":1072},[1058,6822,6823],{"class":1060,"line":1789},[1058,6824,1095],{"emptyLinePlaceholder":1094},[1058,6826,6827],{"class":1060,"line":1794},[1058,6828,6829],{"class":1101},"\u002F\u002F Managed mode requires at least one strong (Shamir) recovery profile —\n",[1058,6831,6832],{"class":1060,"line":1810},[1058,6833,6834],{"class":1101},"\u002F\u002F paper alone has no user-known secret to fall back on.\n",[1058,6836,6837,6839,6841,6844,6846,6849,6851,6853,6855,6857,6859,6861,6863,6866,6868,6870,6872,6874,6876],{"class":1060,"line":2103},[1058,6838,1109],{"class":1108},[1058,6840,1069],{"class":1068},[1058,6842,6843],{"class":1072}," vault",[1058,6845,1133],{"class":1068},[1058,6847,6848],{"class":1072}," recoveryEnrollments ",[1058,6850,1139],{"class":1068},[1058,6852,2093],{"class":1068},[1058,6854,1118],{"class":1064},[1058,6856,1263],{"class":1072},[1058,6858,968],{"class":1068},[1058,6860,868],{"class":1072},[1058,6862,968],{"class":1068},[1058,6864,6865],{"class":1121},"openVaultAndEnrollRecovery",[1058,6867,1124],{"class":1072},[1058,6869,1229],{"class":1068},[1058,6871,1279],{"class":1085},[1058,6873,1229],{"class":1068},[1058,6875,1133],{"class":1068},[1058,6877,1968],{"class":1068},[1058,6879,6880,6882,6884,6886,6888,6890,6892,6894,6896,6898,6900,6903,6905,6907,6909,6912,6914,6916,6918,6920],{"class":1060,"line":2119},[1058,6881,4295],{"class":1218},[1058,6883,1167],{"class":1068},[1058,6885,3519],{"class":1072},[1058,6887,1127],{"class":1068},[1058,6889,4215],{"class":1218},[1058,6891,1167],{"class":1068},[1058,6893,1082],{"class":1068},[1058,6895,325],{"class":1085},[1058,6897,1229],{"class":1068},[1058,6899,1133],{"class":1068},[1058,6901,6902],{"class":1218}," k",[1058,6904,1167],{"class":1068},[1058,6906,2038],{"class":1239},[1058,6908,1133],{"class":1068},[1058,6910,6911],{"class":1218}," n",[1058,6913,1167],{"class":1068},[1058,6915,2973],{"class":1239},[1058,6917,1076],{"class":1068},[1058,6919,3703],{"class":1072},[1058,6921,2377],{"class":1068},[1058,6923,6924,6926],{"class":1060,"line":2130},[1058,6925,1139],{"class":1068},[1058,6927,1142],{"class":1072},[1058,6929,6930],{"class":1060,"line":2135},[1058,6931,6932],{"class":1101},"\u002F\u002F → recoveryEnrollments[0].shares — show once, the hub does not retain them\n",[1573,6934,6936],{"id":6935},"mechanics","Mechanics",[1011,6938,6939,6948,6951,6956],{},[1014,6940,6941,6942,6944,6945,968],{},"The hub generates a random 256-bit secret at first open and seals it under the supplied ",[957,6943,1351],{},", persisting the sealed form at ",[957,6946,6947],{},"_meta\u002Fsealed-secret",[1014,6949,6950],{},"The user never sees or types this secret; it never enters any UI surface.",[1014,6952,6953,6955],{},[957,6954,3144],{}," is unavailable in managed mode — the secret can only be replaced by recovering it under a fresh sealing key.",[1014,6957,6958,6959,6961,6962,6968,6969,1437,6972,1437,6975,1437,6978,1158,6981,6984,6985,6988],{},"Concrete ",[957,6960,1351],{}," implementations ship as separate packages outside the hub — the ",[966,6963,6964,6967],{"href":170},[957,6965,6966],{},"at-*"," family",": ",[957,6970,6971],{},"at-aws-kms",[957,6973,6974],{},"at-azure-keyvault",[957,6976,6977],{},"at-gcp-kms",[957,6979,6980],{},"at-macos-keychain",[957,6982,6983],{},"at-env",". The hub itself carries only the in-memory test provider (",[957,6986,6987],{},"MemorySealer",", shown above).",[1573,6990,6992,6994],{"id":6991},"noydbsealer-interface",[957,6993,1351],{}," interface",[1049,6996,6998],{"className":1051,"code":6997,"language":1053,"meta":1054,"style":1054},"interface NoydbSealer {\n  readonly id: string                                    \u002F\u002F non-secret identifier, safe to log\n  seal(secret: Uint8Array): Promise\u003CUint8Array>       \u002F\u002F called once, at enrollment\n  unseal(sealed: Uint8Array): Promise\u003CUint8Array>         \u002F\u002F called on every unlock; must throw on tamper\n}\n",[957,6999,7000,7009,7023,7053,7079],{"__ignoreMap":1054},[1058,7001,7002,7004,7007],{"class":1060,"line":1061},[1058,7003,1961],{"class":1108},[1058,7005,7006],{"class":1964}," NoydbSealer",[1058,7008,1968],{"class":1068},[1058,7010,7011,7014,7016,7018,7020],{"class":1060,"line":1091},[1058,7012,7013],{"class":1108},"  readonly",[1058,7015,4924],{"class":1218},[1058,7017,1167],{"class":1068},[1058,7019,1978],{"class":1964},[1058,7021,7022],{"class":1101},"                                    \u002F\u002F non-secret identifier, safe to log\n",[1058,7024,7025,7028,7030,7032,7034,7037,7040,7043,7045,7048,7050],{"class":1060,"line":1098},[1058,7026,7027],{"class":1218},"  seal",[1058,7029,1124],{"class":1068},[1058,7031,983],{"class":2610},[1058,7033,1167],{"class":1068},[1058,7035,7036],{"class":1964}," Uint8Array",[1058,7038,7039],{"class":1068},"):",[1058,7041,7042],{"class":1964}," Promise",[1058,7044,2054],{"class":1068},[1058,7046,7047],{"class":1964},"Uint8Array",[1058,7049,2065],{"class":1068},[1058,7051,7052],{"class":1101},"       \u002F\u002F called once, at enrollment\n",[1058,7054,7055,7058,7060,7062,7064,7066,7068,7070,7072,7074,7076],{"class":1060,"line":1105},[1058,7056,7057],{"class":1218},"  unseal",[1058,7059,1124],{"class":1068},[1058,7061,4787],{"class":2610},[1058,7063,1167],{"class":1068},[1058,7065,7036],{"class":1964},[1058,7067,7039],{"class":1068},[1058,7069,7042],{"class":1964},[1058,7071,2054],{"class":1068},[1058,7073,7047],{"class":1964},[1058,7075,2065],{"class":1068},[1058,7077,7078],{"class":1101},"         \u002F\u002F called on every unlock; must throw on tamper\n",[1058,7080,7081],{"class":1060,"line":1762},[1058,7082,2073],{"class":1068},[1573,7084,463],{"id":7085},"threat-model-1",[940,7087,7088],{},"Coercing a user to reveal their secret (the \"rubber-hose\" attack) collapses, because the user has no secret to give up. The attack surface moves to compromising the sealing-key store itself (the OS keyring or KMS), or to compromising enough recovery shares to reach the Shamir threshold — neither of which is coercive against the user. The trade-off is a hard dependency on the sealing-key store: if it is wiped, recovery is the only way back in.",[974,7090,1695],{"id":7091},"api-reference",[1468,7093,7095],{"id":7094},"secret-tier-1","Secret (tier 1)",[1292,7097,7098,7110],{},[1295,7099,7100],{},[1298,7101,7102,7105,7108],{},[1301,7103,7104],{},"Export",[1301,7106,7107],{},"Returns",[1301,7109,3126],{},[1317,7111,7112,7134,7156,7173],{},[1298,7113,7114,7119,7124],{},[1322,7115,7116],{},[957,7117,7118],{},"validateSecret(phrase, opts?)",[1322,7120,7121],{},[957,7122,7123],{},"SecretValidationResult",[1322,7125,7126,7127,7130,7131,7133],{},"Checks a phrase against the format rules without throwing. ",[957,7128,7129],{},"opts"," overrides ",[957,7132,1686],{}," fields.",[1298,7135,7136,7141,7147],{},[1322,7137,7138],{},[957,7139,7140],{},"assertStrongSecret(phrase, opts?)",[1322,7142,7143,7146],{},[957,7144,7145],{},"void"," (throws)",[1322,7148,7149,7150,7152,7153,7155],{},"Same check as ",[957,7151,1743],{},"; throws ",[957,7154,6679],{}," on failure.",[1298,7157,7158,7163,7168],{},[1322,7159,7160],{},[957,7161,7162],{},"db.team.rotateSecret(vault, { oldSecret, newSecret, slotCeremonies? }, factors?)",[1322,7164,7165],{},[957,7166,7167],{},"Promise\u003Cvoid>",[1322,7169,7170,7171,968],{},"Re-derives the old KEK, validates and derives the new one, rewraps every DEK. Gated by ",[957,7172,3144],{},[1298,7174,7175,7180,7185],{},[1322,7176,7177],{},[957,7178,7179],{},"db.team.recoverSecret(vault, { newSecret, recoveryProof, rotateRemainingCodes? }, factors?)",[1322,7181,7182],{},[957,7183,7184],{},"Promise\u003CRecoverSecretResult>",[1322,7186,7187,7188,968],{},"Resets the secret from a recovery proof instead of the old one. Gated by ",[957,7189,3165],{},[940,7191,7192],{},"Types:",[1011,7194,7195,7205,7212],{},[1014,7196,7197,1433,7199,7202,7203,968],{},[957,7198,1686],{},[957,7200,7201],{},"{ minWords?, minWordLength?, rejectRepeatedAdjacent?, pattern?, customValidator? }"," — per-vault secret rule overrides; see ",[966,7204,1576],{"href":5594},[1014,7206,7207,1433,7209,968],{},[957,7208,7123],{},[957,7210,7211],{},"{ ok: true, words: number } | { ok: false, reason: WeakSecretReason, minimum?, got? }",[1014,7213,7214,7216,7217,7219],{},[957,7215,6679],{}," — thrown by ",[957,7218,1797],{}," and by every hub ingress point where validation is on and the phrase fails.",[1468,7221,7223],{"id":7222},"tier-2-authenticator-slots","Tier 2 — authenticator slots",[1292,7225,7226,7236],{},[1295,7227,7228],{},[1298,7229,7230,7232,7234],{},[1301,7231,2233],{},[1301,7233,7107],{},[1301,7235,3126],{},[1317,7237,7238,7254,7270,7291],{},[1298,7239,7240,7245,7249],{},[1322,7241,7242],{},[957,7243,7244],{},"db.team.enrollAuthenticator(vault, options, factors?)",[1322,7246,7247],{},[957,7248,7167],{},[1322,7250,7251,7252,968],{},"Add a slot. Gated by ",[957,7253,3182],{},[1298,7255,7256,7261,7265],{},[1322,7257,7258],{},[957,7259,7260],{},"db.team.removeAuthenticator(vault, slotId, factors?)",[1322,7262,7263],{},[957,7264,7167],{},[1322,7266,7267,7268,968],{},"Remove a slot; idempotent. Gated by ",[957,7269,3201],{},[1298,7271,7272,7277,7281],{},[1322,7273,7274],{},[957,7275,7276],{},"db.team.updateAuthenticator(vault, slotId, options, factors?)",[1322,7278,7279],{},[957,7280,7167],{},[1322,7282,7283,7284,7287,7288,968],{},"Rename\u002Frelabel a slot's ",[957,7285,7286],{},"meta","; id\u002Fmethod\u002Fwrap material are immutable. Gated by ",[957,7289,7290],{},"update-authenticator",[1298,7292,7293,7298,7303],{},[1322,7294,7295],{},[957,7296,7297],{},"db.team.unlockViaAuthenticator(vault, slotId, verify)",[1322,7299,7300],{},[957,7301,7302],{},"Promise\u003CUnlockedKeyring>",[1322,7304,7305,7306,7309,7310,1342],{},"Unlock using an enrolled slot; ",[957,7307,7308],{},"verify"," typically delegates to the matching ",[957,7311,7312],{},"on-*",[940,7314,7192],{},[1011,7316,7317],{},[1014,7318,7319,7322,7323,1437,7325,7327,7328,7330,7331,7334],{},[957,7320,7321],{},"KeyringAuthenticator"," — a discriminated union of wrap-KEK slots (",[957,7324,337],{},[957,7326,309],{},") and wrap-DEKs slots (",[957,7329,313],{},"); see ",[966,7332,7333],{"href":1156},"Tier 2 — Authenticate"," for the full shape.",[1468,7336,7338],{"id":7337},"tier-3-quick-unlock","Tier 3 — quick unlock",[1292,7340,7341,7351],{},[1295,7342,7343],{},[1298,7344,7345,7347,7349],{},[1301,7346,7104],{},[1301,7348,7107],{},[1301,7350,3126],{},[1317,7352,7353,7374,7393,7410],{},[1298,7354,7355,7360,7364],{},[1322,7356,7357],{},[957,7358,7359],{},"db.team.enrollUnlock(vault, state, factors?)",[1322,7361,7362],{},[957,7363,7167],{},[1322,7365,7366,7367,7370,7371,7373],{},"Register a tier-3 quick-unlock state produced by ",[957,7368,7369],{},"enrollPin"," (or another ",[957,7372,7312],{}," tier-3 primitive).",[1298,7375,7376,7381,7386],{},[1322,7377,7378],{},[957,7379,7380],{},"db.team.unlockViaPin(vault, resume)",[1322,7382,7383],{},[957,7384,7385],{},"Promise\u003CUnlockedKeyring | undefined>",[1322,7387,7388,7389,7392],{},"Resume a session via the registered tier-3 state; ",[957,7390,7391],{},"undefined"," if none is registered.",[1298,7394,7395,7402,7407],{},[1322,7396,7397,1824,7400,2614],{},[957,7398,7399],{},"enrollPin(unlockedKeyring, { pin, ttlMs })",[957,7401,1166],{},[1322,7403,7404],{},[957,7405,7406],{},"Promise\u003CQuickUnlockState>",[1322,7408,7409],{},"Mint a resumable tier-3 state from an already-unlocked keyring.",[1298,7411,7412,7419,7423],{},[1322,7413,7414,1824,7417,2614],{},[957,7415,7416],{},"resumePin(state, { pin })",[957,7418,1166],{},[1322,7420,7421],{},[957,7422,7302],{},[1322,7424,7425,7426,1380,7429,968],{},"Verify the PIN and return the cached keyring; throws ",[957,7427,7428],{},"PinExpiredError",[957,7430,7431],{},"PinAttemptsExceededError",[1468,7433,7435],{"id":7434},"policy-gates-methods-and-types","Policy gates (methods and types)",[1292,7437,7438,7448],{},[1295,7439,7440],{},[1298,7441,7442,7444,7446],{},[1301,7443,2233],{},[1301,7445,7107],{},[1301,7447,3126],{},[1317,7449,7450,7468],{},[1298,7451,7452,7457,7461],{},[1322,7453,7454],{},[957,7455,7456],{},"db.checkGate(vault, gate, factors?)",[1322,7458,7459,7146],{},[957,7460,7167],{},[1322,7462,7463,7464,7467],{},"Evaluate a gate against the active session tier and presented factors; throws ",[957,7465,7466],{},"PolicyDeniedError"," on denial.",[1298,7469,7470,7475,7480],{},[1322,7471,7472],{},[957,7473,7474],{},"db.updatePolicy(vault, override)",[1322,7476,7477],{},[957,7478,7479],{},"Promise\u003CVaultPolicy>",[1322,7481,7482,7483,7485,7486,968],{},"Replace the policy document at ",[957,7484,2879],{},"; unspecified gates keep their previous values. Gated by ",[957,7487,3238],{},[940,7489,7192],{},[1011,7491,7492,7499,7507,7514,7531],{},[1014,7493,7494,1433,7496,968],{},[957,7495,2875],{},[957,7497,7498],{},"{ secret?: SecretPolicy, gates: Partial\u003CRecord\u003CGateName, GatePolicy>> }",[1014,7500,7501,1433,7504,968],{},[957,7502,7503],{},"GatePolicy",[957,7505,7506],{},"{ minTier: 1 | 2 | 3, factors?, warn?, enabled? }",[1014,7508,7509,1433,7511,968],{},[957,7510,2991],{},[957,7512,7513],{},"{ anyOf: FactorKind[], count?, freshnessMs? }",[1014,7515,7516,7216,7518,7520,7521,1437,7524,7527,7528,968],{},[957,7517,7466],{},[957,7519,3610],{},"; carries ",[957,7522,7523],{},".gate",[957,7525,7526],{},".required",", and ",[957,7529,7530],{},".reason",[1014,7532,7533,1380,7535,7537,7538,1696],{},[957,7534,1042],{},[957,7536,3135],{}," — the two shipped policy presets (",[957,7539,992],{},[1468,7541,7543],{"id":7542},"echo-secret-mode-tier-1","Echo-secret mode (tier 1)",[1292,7545,7546,7556],{},[1295,7547,7548],{},[1298,7549,7550,7552,7554],{},[1301,7551,7104],{},[1301,7553,7107],{},[1301,7555,3126],{},[1317,7557,7558,7583,7600,7614,7636,7653,7673],{},[1298,7559,7560,7565,7570],{},[1322,7561,7562],{},[957,7563,7564],{},"beginEchoUnlock(store, vault, { userId, prompt, deviceSeal? })",[1322,7566,7567],{},[957,7568,7569],{},"Promise\u003CEchoCeremony>",[1322,7571,7572,7573,1380,7576,1380,7579,1380,7581,968],{},"Start the interactive ceremony: read the keyring, refuse an expired slot, verify the prompt, resolve the echo. Throws ",[957,7574,7575],{},"NoAccessError",[957,7577,7578],{},"KeyringExpiredError",[957,7580,4651],{},[957,7582,5118],{},[1298,7584,7585,7589,7594],{},[1322,7586,7587],{},[957,7588,5125],{},[1322,7590,7591],{},[957,7592,7593],{},"string | null",[1322,7595,7596,7597,7599],{},"The echo to display for recognition; ",[957,7598,5292],{}," means the degraded typed-echo path.",[1298,7601,7602,7606,7611],{},[1322,7603,7604],{},[957,7605,5502],{},[1322,7607,7608],{},[957,7609,7610],{},"string | undefined",[1322,7612,7613],{},"The keyring's display-masking hint, if the owner set one.",[1298,7615,7616,7621,7625],{},[1322,7617,7618],{},[957,7619,7620],{},"ceremony.complete({ echo?, key })",[1322,7622,7623],{},[957,7624,7302],{},[1322,7626,7627,7628,7630,7631,4956,7633,7635],{},"Derive the KEK from all three parts and unlock. ",[957,7629,1532],{}," is required when ",[957,7632,4759],{},[957,7634,5292],{},". Stateless — retryable.",[1298,7637,7638,7643,7647],{},[1322,7639,7640],{},[957,7641,7642],{},"validateEchoSecret(parts, opts?)",[1322,7644,7645],{},[957,7646,7123],{},[1322,7648,7649,7650,7652],{},"Check a ",[957,7651,4644],{}," struct against the per-part floors without throwing.",[1298,7654,7655,7660,7664],{},[1322,7656,7657],{},[957,7658,7659],{},"assertStrongEchoSecret(parts, opts?)",[1322,7661,7662,7146],{},[957,7663,7145],{},[1322,7665,7666,7667,6481,7669,7672],{},"Same check; throws ",[957,7668,6679],{},[957,7670,7671],{},"opts.allowWeakSecret"," skips it.",[1298,7674,7675,7679,7682],{},[1322,7676,7677],{},[957,7678,4966],{},[1322,7680,7681],{},"class",[1322,7683,7684,7685,7687,7688,7690],{},"The only ",[957,7686,4955],{}," shipped in ",[957,7689,992],{}," (in-memory, test-only).",[940,7692,7693,7694,7696,7697,7699],{},"Options on ",[957,7695,1827],{}," (echo mode only — each throws ",[957,7698,4651],{}," outside it):",[1011,7701,7702,7714,7726,7734],{},[1014,7703,7704,7706,7707,7709,7710,7713],{},[957,7705,4378],{}," — selects the mode; ",[957,7708,983],{}," must then be an ",[957,7711,7712],{},"EchoSecretParts"," struct.",[1014,7715,7716,7719,7720,7723,7724,968],{},[957,7717,7718],{},"deviceSeal?: NoydbDeviceSeal"," — present ⇒ the keyring enrolls ",[957,7721,7722],{},"reveal: 'sealed'","; absent ⇒ ",[957,7725,4774],{},[1014,7727,7728,7731,7732,968],{},[957,7729,7730],{},"echoMaskHint?: string"," — stored on the keyring, surfaced later as ",[957,7733,5502],{},[1014,7735,7736,7739,7740,7742],{},[957,7737,7738],{},"echoSecretPolicy?: EchoSecretPolicy"," — per-vault prompt \u002F combined floors, applied when ",[957,7741,1743],{}," is on.",[940,7744,7745],{},"Options on rotation and recovery:",[1011,7747,7748,7754,7760,7769],{},[1014,7749,7750,7753],{},[957,7751,7752],{},"RotateSecretInput.newSecret: string | EchoSecretParts"," — the shape selects the mode after rotation.",[1014,7755,7756,7759],{},[957,7757,7758],{},"RotateSecretInput.allowModeDowngrade?: boolean"," — required to rotate an echo keyring to a string secret.",[1014,7761,7762,7765,7766,7768],{},[957,7763,7764],{},"RotateSecretInput.echoOptions?: { deviceSeal?, maskHint? }"," — enrollment knobs for the block minted when ",[957,7767,5916],{}," is 3-part.",[1014,7770,7771,1380,7774,5790,7777,968],{},[957,7772,7773],{},"RotateSecretInput.echoSecretPolicy?",[957,7775,7776],{},"RecoverSecretInput.echoSecretPolicy?",[957,7778,5793],{},[940,7780,7192],{},[1011,7782,7783,7791,7803,7811,7819,7828,7835],{},[1014,7784,7785,1433,7787,7790],{},[957,7786,7712],{},[957,7788,7789],{},"{ prompt: string, echo: string, key: string }","; the secret shape for echo mode.",[1014,7792,7793,1433,7796,7799,7800,968],{},[957,7794,7795],{},"EchoCeremony",[957,7797,7798],{},"{ reveal, maskHint, complete() }","; see ",[966,7801,4978],{"href":7802},"#the-unlock-ceremony",[1014,7804,7805,1433,7808,968],{},[957,7806,7807],{},"EchoSecretPolicy",[957,7809,7810],{},"{ prompt?: SecretPolicy, combined?: SecretPolicy }",[1014,7812,7813,1433,7815,7818],{},[957,7814,6477],{},[957,7816,7817],{},"EchoSecretParts & { reveal?: 'portable' | 'none' }","; a pod recipient's echo secret.",[1014,7820,7821,7824,7825,1696],{},[957,7822,7823],{},"KeyringEchoBlock"," — the on-keyring declaration (verifiers, reveal policy, optional ",[957,7826,7827],{},"mask_hint",[1014,7829,7830,1433,7832,7834],{},[957,7831,4955],{},[957,7833,4959],{},"; seals only the echo reveal blob.",[1014,7836,7837,1380,7839,1380,7841,7843,7844,968],{},[957,7838,5428],{},[957,7840,5118],{},[957,7842,5301],{}," — see ",[966,7845,6573],{"href":7846},"#errors",[1468,7848,7850],{"id":7849},"managed-secret-mode-methods-and-types","Managed-secret mode (methods and types)",[1292,7852,7853,7863],{},[1295,7854,7855],{},[1298,7856,7857,7859,7861],{},[1301,7858,7104],{},[1301,7860,7107],{},[1301,7862,3126],{},[1317,7864,7865,7883],{},[1298,7866,7867,7872,7877],{},[1322,7868,7869],{},[957,7870,7871],{},"db.team.openVaultAndEnrollRecovery(vault, { recovery, locale? })",[1322,7873,7874],{},[957,7875,7876],{},"Promise\u003C{ vault, recoveryEnrollments }>",[1322,7878,7879,7880,7882],{},"Atomic create-and-enroll for managed-mode vaults — required because ",[957,7881,3165],{}," cannot be left unenrolled.",[1298,7884,7885,7889,7891],{},[1322,7886,7887],{},[957,7888,6987],{},[1322,7890,7681],{},[1322,7892,7684,7893,7895,7896,7898],{},[957,7894,1351],{}," implementation shipped in ",[957,7897,992],{}," today (in-memory, test-only).",[940,7900,7192],{},[1011,7902,7903],{},[1014,7904,7905,1433,7907,7799,7910,968],{},[957,7906,1351],{},[957,7908,7909],{},"{ id, seal(secret), unseal(sealed) }",[966,7911,7913,6994],{"href":7912},"#noydbsealer-interface",[957,7914,1351],{},[974,7916,7918],{"id":7917},"threat-model-summary","Threat model summary",[1292,7920,7921,7937],{},[1295,7922,7923],{},[1298,7924,7925,7928,7931,7934],{},[1301,7926,7927],{},"Attack",[1301,7929,7930],{},"Standard mode",[1301,7932,7933],{},"Echo mode",[1301,7935,7936],{},"Managed mode",[1317,7938,7939,7953,7967,7987,8001,8013,8028,8040,8054,8066],{},[1298,7940,7941,7944,7947,7950],{},[1322,7942,7943],{},"Brute-force the secret",[1322,7945,7946],{},"PBKDF2 600k iterations + phrase format (≥77 bits)",[1322,7948,7949],{},"Same 600k, over all three parts jointly",[1322,7951,7952],{},"Random 256 bits — infeasible",[1298,7954,7955,7958,7961,7964],{},[1322,7956,7957],{},"Phishing page imitating the unlock screen",[1322,7959,7960],{},"Nothing structural — one typed field looks like any other",[1322,7962,7963],{},"Defeated: the page cannot produce the echo, and no single-field entry exists for it to imitate",[1322,7965,7966],{},"Nothing to phish — the user types no secret",[1298,7968,7969,7972,7975,7984],{},[1322,7970,7971],{},"Stolen copy of the keyring file",[1322,7973,7974],{},"Ciphertext plus the PBKDF2 floor",[1322,7976,7977,7978,7980,7981,7983],{},"Same, plus offline guessing oracles on the prompt and the echo; a ",[957,7979,4787],{}," reveal keeps the echo unreadable, a ",[957,7982,4774],{}," one does not",[1322,7985,7986],{},"Same, plus a sealed secret only the sealing-key store can open",[1298,7988,7989,7992,7995,7998],{},[1322,7990,7991],{},"Stolen device with a platform passkey (tier 2)",[1322,7993,7994],{},"Tier-1 changes require an off-device 2FA factor",[1322,7996,7997],{},"Same",[1322,7999,8000],{},"Same; secret rotation is disabled entirely",[1298,8002,8003,8006,8009,8011],{},[1322,8004,8005],{},"Stolen device with a cached tier-3 PIN",[1322,8007,8008],{},"Idle timeout wipes the cache",[1322,8010,7997],{},[1322,8012,7997],{},[1298,8014,8015,8018,8023,8025],{},[1322,8016,8017],{},"Coerced user",[1322,8019,8020,8022],{},[957,8021,1458],{}," duress-secret wipe (plausible deniability)",[1322,8024,7997],{},[1322,8026,8027],{},"User has no secret to reveal",[1298,8029,8030,8033,8036,8038],{},[1322,8031,8032],{},"Lost passkey only",[1322,8034,8035],{},"Unlock via another tier-2 slot, or recovery",[1322,8037,7997],{},[1322,8039,7997],{},[1298,8041,8042,8045,8048,8051],{},[1322,8043,8044],{},"Forgotten secret",[1322,8046,8047],{},"Paper or Shamir recovery",[1322,8049,8050],{},"Same, and the revealed echo is itself a cue for the key",[1322,8052,8053],{},"Same, and recovery is mandatory",[1298,8055,8056,8059,8062,8064],{},[1322,8057,8058],{},"Compromised admin",[1322,8060,8061],{},"Cannot read user data (zero-knowledge); cannot rotate a user's secret",[1322,8063,7997],{},[1322,8065,7997],{},[1298,8067,8068,8071,8074,8076],{},[1322,8069,8070],{},"Forged keyring file",[1322,8072,8073],{},"Authenticated AES-GCM rejects modified envelopes",[1322,8075,7997],{},[1322,8077,7997],{},[974,8079,8081],{"id":8080},"limits-and-gotchas","Limits and gotchas",[1011,8083,8084,8096,8111,8117,8133,8143,8154,8167,8173],{},[1014,8085,8086,8089,8090,8092,8093,968],{},[943,8087,8088],{},"Tier-2 slots don't survive rotation"," unless you supply a ",[957,8091,3959],{}," ceremony per slot — see ",[966,8094,3813],{"href":8095},"#rotate-and-recover-the-secret",[1014,8097,8098,8105,8106,1158,8108,8110],{},[943,8099,8100,8101,1158,8103],{},"Recovery profiles ",[957,8102,4134],{},[957,8104,4150],{}," have no implementation yet; only ",[957,8107,4099],{},[957,8109,325],{}," work.",[1014,8112,8113,8116],{},[943,8114,8115],{},"Per-keyring policy overrides"," are stored but not enforced — the vault-level policy is always the effective one.",[1014,8118,8119,1824,8125,8127,8128,960,8130,8132],{},[943,8120,8121,8122,8124],{},"Secret validation on first creation and on ",[957,8123,1833],{}," is opt-in",[957,8126,1837],{},"); on ",[957,8129,3837],{},[957,8131,4177],{}," it is on by default.",[1014,8134,8135,8140,8141,968],{},[943,8136,6958,8137,8139],{},[957,8138,1351],{},"s for real platforms are not published yet"," — only the in-memory test provider ships in ",[957,8142,992],{},[1014,8144,8145,8150,8151,8153],{},[943,8146,6958,8147,8149],{},[957,8148,4955],{},"s are equally unshipped"," — without one, echo keyrings enroll a ",[957,8152,4774],{}," reveal, which a file-holder who knows the prompt can decrypt.",[1014,8155,8156,8159,8160,8163,8164,8166],{},[943,8157,8158],{},"An echo keyring cannot be opened by any single string"," — including a ",[957,8161,8162],{},"#","-joined form. Tooling that assumes a one-field secret gets ",[957,8165,5428],{},"; there is no compatibility path, by design.",[1014,8168,8169,8172],{},[943,8170,8171],{},"The anti-phishing terminal state is the player's job."," The kernel exposes the reveal and keeps no attempt counter; showing the warning, refusing a skip affordance, and rate-limiting wrong prompts are all app-side obligations.",[1014,8174,8175,8178,8179,968],{},[943,8176,8177],{},"Anti-autobiography guidance is documentation, never a validator"," — the hub cannot tell a private memory from a fact on someone's public profile. See the ",[966,8180,5782],{"href":540},[974,8182,8184],{"id":8183},"see-also","See also",[1011,8186,8187,8214,8222,8227,8232],{},[1014,8188,8189,8195,8196,1437,8198,1437,8200,1437,8202,1437,8204,1437,8206,1437,8208,1437,8210,1437,8212,1696],{},[966,8190,8192,6967],{"href":8191},"..\u002Ffamilies\u002Fon\u002F",[957,8193,8194],{},"on"," — the full catalog of unlock\u002Fauth packages (",[957,8197,1379],{},[957,8199,1383],{},[957,8201,1386],{},[957,8203,1410],{},[957,8205,1436],{},[957,8207,1440],{},[957,8209,1443],{},[957,8211,1446],{},[957,8213,1458],{},[1014,8215,8216,8221],{},[966,8217,8219],{"href":8218},".\u002Fcover",[957,8220,645],{}," — the plaintext, unauthenticated vault metadata surface (separate from anything covered by session tiers).",[1014,8223,8224,8226],{},[966,8225,539],{"href":540}," — how to choose the three parts, and what makes an echo unguessable.",[1014,8228,8229,8231],{},[966,8230,463],{"href":464}," — the attacker positions behind every claim on this page, including the echo ceremony's honest limits.",[1014,8233,8234,8236],{},[966,8235,589],{"href":590}," — the full tier-1\u002F2\u002F3 lifecycle end to end.",[8238,8239,8240],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":1054,"searchDepth":1098,"depth":1098,"links":8242},[8243,8244,8245,8246,8286,8294,8295,8296],{"id":976,"depth":1091,"text":977},{"id":1031,"depth":1091,"text":1032},{"id":1289,"depth":1091,"text":1290},{"id":1462,"depth":1091,"text":1463,"children":8247},[8248,8253,8257,8258,8264,8268,8280],{"id":1470,"depth":1098,"text":1471,"children":8249},[8250,8251,8252],{"id":1575,"depth":1105,"text":1576},{"id":1699,"depth":1105,"text":1700},{"id":1914,"depth":1105,"text":1915},{"id":1940,"depth":1098,"text":1941,"children":8254},[8255,8256],{"id":2223,"depth":1105,"text":2224},{"id":2308,"depth":1105,"text":2309},{"id":2652,"depth":1098,"text":2653},{"id":2868,"depth":1098,"text":2869,"children":8259},[8260,8261,8262,8263],{"id":3110,"depth":1105,"text":3111},{"id":3300,"depth":1105,"text":3301},{"id":3562,"depth":1105,"text":3563},{"id":3794,"depth":1105,"text":3795},{"id":3812,"depth":1098,"text":3813,"children":8265},[8266,8267],{"id":3816,"depth":1105,"text":3817},{"id":4064,"depth":1105,"text":4065},{"id":4363,"depth":1098,"text":4364,"children":8269},[8270,8271,8272,8273,8274,8275,8276,8277,8278,8279],{"id":4423,"depth":1105,"text":4424},{"id":4741,"depth":1105,"text":4742},{"id":4977,"depth":1105,"text":4978},{"id":5317,"depth":1105,"text":5318},{"id":5432,"depth":1105,"text":5433},{"id":5510,"depth":1105,"text":5511},{"id":5906,"depth":1105,"text":5907},{"id":6260,"depth":1105,"text":6261},{"id":6572,"depth":1105,"text":6573},{"id":6694,"depth":1105,"text":463},{"id":6707,"depth":1098,"text":6708,"children":8281},[8282,8283,8285],{"id":6935,"depth":1105,"text":6936},{"id":6991,"depth":1105,"text":8284},"NoydbSealer interface",{"id":7085,"depth":1105,"text":463},{"id":7091,"depth":1091,"text":1695,"children":8287},[8288,8289,8290,8291,8292,8293],{"id":7094,"depth":1098,"text":7095},{"id":7222,"depth":1098,"text":7223},{"id":7337,"depth":1098,"text":7338},{"id":7434,"depth":1098,"text":7435},{"id":7542,"depth":1098,"text":7543},{"id":7849,"depth":1098,"text":7850},{"id":7917,"depth":1091,"text":7918},{"id":8080,"depth":1091,"text":8081},{"id":8183,"depth":1091,"text":8184},null,"The three-tier session-authentication ladder — secret, multi-slot, quick-resume — and the policy-gate DSL guarding sensitive vault operations.","md",{},"service",{"title":844,"description":8298},"stable","important","_DabOI_fPjST05PVaxeHyOeSAbFGc0ESK5MebvdEnng",{"data":8307,"body":8308},{},{"type":8309,"children":8310},"root",[8311],{"type":8312,"tag":940,"props":8313,"children":8314},"element",{},[8315],{"type":1670,"value":8316},"collaboration-and-auth",{"data":8318,"body":8319},{},{"type":8309,"children":8320},[8321],{"type":8312,"tag":940,"props":8322,"children":8323},{},[8324,8329,8331,8336,8338,8343],{"type":8312,"tag":957,"props":8325,"children":8327},{"className":8326},[],[8328],{"type":1670,"value":992},{"type":1670,"value":8330}," (core) — session tiers, policy gates, and secret lifecycle ship in ",{"type":8312,"tag":957,"props":8332,"children":8334},{"className":8333},[],[8335],{"type":1670,"value":992},{"type":1670,"value":8337},"'s main entry point; tier-2\u002F3 factor packages are the ",{"type":8312,"tag":957,"props":8339,"children":8341},{"className":8340},[],[8342],{"type":1670,"value":1148},{"type":1670,"value":6967},{"data":8345,"body":8346},{},{"type":8309,"children":8347},[8348],{"type":8312,"tag":940,"props":8349,"children":8350},{},[8351,8353,8358],{"type":1670,"value":8352},"none — always on for every ",{"type":8312,"tag":957,"props":8354,"children":8356},{"className":8355},[],[8357],{"type":1670,"value":1038},{"type":1670,"value":8359}," call",{"data":8361,"body":8362},{},{"type":8309,"children":8363},[8364],{"type":8312,"tag":940,"props":8365,"children":8366},{},[8367,8369,8374,8376,8381],{"type":1670,"value":8368},"every ",{"type":8312,"tag":957,"props":8370,"children":8372},{"className":8371},[],[8373],{"type":1670,"value":1148},{"type":1670,"value":8375}," unlock package, plus ",{"type":8312,"tag":957,"props":8377,"children":8379},{"className":8378},[],[8380],{"type":1670,"value":992},{"type":1670,"value":8382},"'s KEK derivation, keyring layout, and session module.",1791896590575]