[{"data":1,"prerenderedAt":2733},["ShallowReactive",2],{"navigation_docs_en":3,"-en-auth-policy":441,"-en-auth-policy-surround":2728},[4,45,60,81,112,129,146,173,197,213,242,279,356,372,384,403,429],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"queue","i-lucide-layers","\u002Fen\u002Fqueue","en\u002F10.queue",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Overview","\u002Fen\u002Fqueue\u002Foverview","en\u002F10.queue\u002F1.overview","i-lucide-list-ordered",{"title":57,"path":58,"stem":59,"icon":47},"Tasks","\u002Fen\u002Fqueue\u002Ftasks","en\u002F10.queue\u002F2.tasks",{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":44},"events","i-lucide-workflow","\u002Fen\u002Fevents","en\u002F11.events",[66,71,76],{"title":67,"path":68,"stem":69,"icon":70},"Events","\u002Fen\u002Fevents\u002Foverview","en\u002F11.events\u002F1.overview","i-lucide-radio",{"title":72,"path":73,"stem":74,"icon":75},"Outbox","\u002Fen\u002Fevents\u002Foutbox","en\u002F11.events\u002F2.outbox","i-lucide-inbox",{"title":77,"path":78,"stem":79,"icon":80},"Scheduling","\u002Fen\u002Fevents\u002Fscheduler","en\u002F11.events\u002F3.scheduler","i-lucide-calendar-clock",{"title":82,"icon":83,"path":84,"stem":85,"children":86,"page":44},"auth","i-lucide-shield-check","\u002Fen\u002Fauth","en\u002F12.auth",[87,92,97,102,107],{"title":88,"path":89,"stem":90,"icon":91},"Authentication","\u002Fen\u002Fauth\u002Foverview","en\u002F12.auth\u002F1.overview","i-lucide-key-round",{"title":93,"path":94,"stem":95,"icon":96},"RBAC","\u002Fen\u002Fauth\u002Frbac","en\u002F12.auth\u002F2.rbac","i-lucide-users",{"title":98,"path":99,"stem":100,"icon":101},"Policies","\u002Fen\u002Fauth\u002Fpolicy","en\u002F12.auth\u002F3.policy","i-lucide-gavel",{"title":103,"path":104,"stem":105,"icon":106},"Using auth standalone","\u002Fen\u002Fauth\u002Fstandalone","en\u002F12.auth\u002F4.standalone","i-lucide-plug",{"title":108,"path":109,"stem":110,"icon":111},"Social login with goth, standalone","\u002Fen\u002Fauth\u002Fsocial-standalone","en\u002F12.auth\u002F5.social-standalone","i-lucide-log-in",{"title":113,"icon":114,"path":115,"stem":116,"children":117,"page":44},"mail","i-lucide-mail","\u002Fen\u002Fmail","en\u002F13.mail",[118,121,125],{"title":52,"path":119,"stem":120,"icon":114},"\u002Fen\u002Fmail\u002Foverview","en\u002F13.mail\u002F1.overview",{"title":122,"path":123,"stem":124,"icon":114},"SMTP","\u002Fen\u002Fmail\u002Fsmtp","en\u002F13.mail\u002F2.smtp",{"title":126,"path":127,"stem":128,"icon":114},"MJML","\u002Fen\u002Fmail\u002Fmjml","en\u002F13.mail\u002F3.mjml",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":44},"notify","i-lucide-bell","\u002Fen\u002Fnotify","en\u002F14.notify",[135,138,142],{"title":52,"path":136,"stem":137,"icon":131},"\u002Fen\u002Fnotify\u002Foverview","en\u002F14.notify\u002F1.overview",{"title":139,"path":140,"stem":141,"icon":131},"Database channel","\u002Fen\u002Fnotify\u002Fdatabase","en\u002F14.notify\u002F2.database",{"title":143,"path":144,"stem":145,"icon":131},"Broadcast channel","\u002Fen\u002Fnotify\u002Fbroadcast","en\u002F14.notify\u002F3.broadcast",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":44},"storage","i-lucide-hard-drive","\u002Fen\u002Fstorage","en\u002F15.storage",[152,155,159,163,168],{"title":52,"path":153,"stem":154,"icon":148},"\u002Fen\u002Fstorage\u002Foverview","en\u002F15.storage\u002F1.overview",{"title":156,"path":157,"stem":158},"Disk API","\u002Fen\u002Fstorage\u002Fdisk-api","en\u002F15.storage\u002F2.disk-api",{"title":160,"path":161,"stem":162,"icon":148},"Memory and local disk","\u002Fen\u002Fstorage\u002Flocal","en\u002F15.storage\u002F3.local",{"title":164,"path":165,"stem":166,"icon":167},"S3","\u002Fen\u002Fstorage\u002Fs3","en\u002F15.storage\u002F4.s3","i-lucide-cloud",{"title":169,"path":170,"stem":171,"icon":172},"Testing","\u002Fen\u002Fstorage\u002Ftesting","en\u002F15.storage\u002F5.testing","i-lucide-flask-conical",{"title":174,"icon":175,"path":176,"stem":177,"children":178,"page":44},"telemetry","i-lucide-activity","\u002Fen\u002Ftelemetry","en\u002F16.telemetry",[179,182,187,192],{"title":52,"path":180,"stem":181,"icon":175},"\u002Fen\u002Ftelemetry\u002Foverview","en\u002F16.telemetry\u002F1.overview",{"title":183,"path":184,"stem":185,"icon":186},"Logging","\u002Fen\u002Ftelemetry\u002Flogging","en\u002F16.telemetry\u002F2.logging","i-lucide-scroll-text",{"title":188,"path":189,"stem":190,"icon":191},"OpenTelemetry","\u002Fen\u002Ftelemetry\u002Fopentelemetry","en\u002F16.telemetry\u002F3.opentelemetry","i-lucide-radar",{"title":193,"path":194,"stem":195,"icon":196},"Sentry","\u002Fen\u002Ftelemetry\u002Fsentry","en\u002F16.telemetry\u002F4.sentry","i-lucide-bug",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F17.reference",[203,208],{"title":204,"path":205,"stem":206,"icon":207},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F17.reference\u002F1.configuration","i-lucide-settings",{"title":209,"path":210,"stem":211,"icon":212},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F17.reference\u002F2.dependencies","i-lucide-package",{"title":214,"icon":215,"path":216,"stem":217,"children":218,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[219,223,228,232,237],{"title":220,"path":221,"stem":222,"icon":47},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture",{"title":224,"path":225,"stem":226,"icon":227},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F2.error-model","i-lucide-shield-alert",{"title":204,"path":229,"stem":230,"icon":231},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F3.configuration","i-lucide-settings-2",{"title":233,"path":234,"stem":235,"icon":236},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F4.codegen-pipeline","i-lucide-file-json",{"title":238,"path":239,"stem":240,"icon":241},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F5.design-patterns","i-lucide-puzzle",{"title":243,"icon":244,"path":245,"stem":246,"children":247,"page":44},"Kit","i-lucide-box","\u002Fen\u002Fkit","en\u002F3.kit",[248,251,256,261,266,270,275],{"title":52,"path":249,"stem":250,"icon":244},"\u002Fen\u002Fkit\u002Foverview","en\u002F3.kit\u002F1.overview",{"title":252,"path":253,"stem":254,"icon":255},"Application lifecycle","\u002Fen\u002Fkit\u002Fapp","en\u002F3.kit\u002F2.app","i-lucide-power",{"title":257,"path":258,"stem":259,"icon":260},"Server","\u002Fen\u002Fkit\u002Fserver","en\u002F3.kit\u002F3.server","i-lucide-server",{"title":262,"path":263,"stem":264,"icon":265},"Worker","\u002Fen\u002Fkit\u002Fworker","en\u002F3.kit\u002F4.worker","i-lucide-cog",{"title":267,"path":268,"stem":269,"icon":70},"Realtime","\u002Fen\u002Fkit\u002Frealtime","en\u002F3.kit\u002F5.realtime",{"title":271,"path":272,"stem":273,"icon":274},"Migrations","\u002Fen\u002Fkit\u002Fmigrations","en\u002F3.kit\u002F6.migrations","i-lucide-file-stack",{"title":276,"path":277,"stem":278,"icon":111},"Social login","\u002Fen\u002Fkit\u002Fsocial-login","en\u002F3.kit\u002F7.social-login",{"title":280,"icon":281,"path":282,"stem":283,"children":284,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F4.cli",[285,288,293,298,303,308,313,318,323,328,333,337,342,347,351],{"title":52,"path":286,"stem":287,"icon":281},"\u002Fen\u002Fcli\u002Foverview","en\u002F4.cli\u002F1.overview",{"title":289,"path":290,"stem":291,"icon":292},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F4.cli\u002F10.add-db","i-lucide-database-zap",{"title":294,"path":295,"stem":296,"icon":297},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F4.cli\u002F11.add-compose","i-lucide-container",{"title":299,"path":300,"stem":301,"icon":302},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F4.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":304,"path":305,"stem":306,"icon":307},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F4.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":309,"path":310,"stem":311,"icon":312},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F4.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":314,"path":315,"stem":316,"icon":317},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F4.cli\u002F15.add-seeder","i-lucide-sprout",{"title":319,"path":320,"stem":321,"icon":322},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F4.cli\u002F2.new-project","i-lucide-folder-plus",{"title":324,"path":325,"stem":326,"icon":327},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F4.cli\u002F3.new-module","i-lucide-blocks",{"title":329,"path":330,"stem":331,"icon":332},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F4.cli\u002F4.add-surface","i-lucide-layers-2",{"title":334,"path":335,"stem":336,"icon":244},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F4.cli\u002F5.add-core",{"title":338,"path":339,"stem":340,"icon":341},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F4.cli\u002F6.add-handler","i-lucide-webhook",{"title":343,"path":344,"stem":345,"icon":346},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F4.cli\u002F7.new-migration","i-lucide-file-plus",{"title":348,"path":349,"stem":350,"icon":265},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F4.cli\u002F8.worker-generators",{"title":352,"path":353,"stem":354,"icon":355},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F4.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"title":357,"icon":227,"path":358,"stem":359,"children":360,"page":44},"errs","\u002Fen\u002Ferrs","en\u002F5.errs",[361,364,368],{"title":52,"path":362,"stem":363,"icon":227},"\u002Fen\u002Ferrs\u002Foverview","en\u002F5.errs\u002F1.overview",{"title":365,"path":366,"stem":367},"API","\u002Fen\u002Ferrs\u002Fapi","en\u002F5.errs\u002F2.api",{"title":369,"path":370,"stem":371,"icon":227},"Integration","\u002Fen\u002Ferrs\u002Fintegration","en\u002F5.errs\u002F3.integration",{"title":373,"icon":231,"path":374,"stem":375,"children":376,"page":44},"envconf","\u002Fen\u002Fenvconf","en\u002F6.envconf",[377,380],{"title":52,"path":378,"stem":379,"icon":231},"\u002Fen\u002Fenvconf\u002Foverview","en\u002F6.envconf\u002F1.overview",{"title":381,"path":382,"stem":383},"Recipes","\u002Fen\u002Fenvconf\u002Frecipes","en\u002F6.envconf\u002F2.recipes",{"title":385,"icon":386,"path":387,"stem":388,"children":389,"page":44},"httperr","i-lucide-globe","\u002Fen\u002Fhttperr","en\u002F7.httperr",[390,393,398],{"title":52,"path":391,"stem":392,"icon":386},"\u002Fen\u002Fhttperr\u002Foverview","en\u002F7.httperr\u002F1.overview",{"title":394,"path":395,"stem":396,"icon":397},"Error responses","\u002Fen\u002Fhttperr\u002Ferror-responses","en\u002F7.httperr\u002F2.error-responses","i-lucide-octagon-alert",{"title":399,"path":400,"stem":401,"icon":402},"Validation","\u002Fen\u002Fhttperr\u002Fvalidation","en\u002F7.httperr\u002F3.validation","i-lucide-badge-check",{"title":404,"icon":405,"path":406,"stem":407,"children":408,"page":44},"dbx","i-lucide-database","\u002Fen\u002Fdbx","en\u002F8.dbx",[409,412,416,420,425],{"title":52,"path":410,"stem":411,"icon":405},"\u002Fen\u002Fdbx\u002Foverview","en\u002F8.dbx\u002F1.overview",{"title":413,"path":414,"stem":415,"icon":106},"pgx","\u002Fen\u002Fdbx\u002Fpg","en\u002F8.dbx\u002F2.pg",{"title":417,"path":418,"stem":419,"icon":47},"bun","\u002Fen\u002Fdbx\u002Fbunx","en\u002F8.dbx\u002F3.bunx",{"title":421,"path":422,"stem":423,"icon":424},"Transactions","\u002Fen\u002Fdbx\u002Ftransactions","en\u002F8.dbx\u002F4.transactions","i-lucide-git-merge",{"title":426,"path":427,"stem":428,"icon":317},"Seeders","\u002Fen\u002Fdbx\u002Fseed","en\u002F8.dbx\u002F5.seed",{"title":430,"icon":55,"path":431,"stem":432,"children":433,"page":44},"paginate","\u002Fen\u002Fpaginate","en\u002F9.paginate",[434,437],{"title":52,"path":435,"stem":436,"icon":55},"\u002Fen\u002Fpaginate\u002Foverview","en\u002F9.paginate\u002F1.overview",{"title":438,"path":439,"stem":440,"icon":19},"Cursor pagination","\u002Fen\u002Fpaginate\u002Fcursor","en\u002F9.paginate\u002F2.cursor",{"id":442,"title":98,"body":443,"description":2721,"extension":2722,"links":2723,"meta":2724,"navigation":2725,"path":99,"seo":2726,"stem":100,"__hash__":2727},"docs_en\u002Fen\u002F12.auth\u002F3.policy.md",{"type":444,"value":445,"toc":2714},"minimark",[446,455,484,517,520,525,532,592,644,800,806,885,899,903,906,970,977,984,1134,1141,1266,1276,1639,1655,1669,1692,1696,1699,1794,1805,1809,1830,2453,2471,2478,2588,2604,2608,2621,2693,2710],[447,448,449,450,454],"p",{},"Import the ",[451,452,453],"code",{},"policy"," subpackage to declare per-request authorization checks:",[456,457,462],"pre",{"className":458,"code":459,"language":460,"meta":461,"style":461},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import \"github.com\u002Fgp-system\u002Fauth\u002Fpolicy\"\n","go","",[451,463,464],{"__ignoreMap":461},[465,466,469,473,477,481],"span",{"class":467,"line":468},"line",1,[465,470,472],{"class":471},"s7zQu","import",[465,474,476],{"class":475},"sMK4o"," \"",[465,478,480],{"class":479},"sBMFI","github.com\u002Fgp-system\u002Fauth\u002Fpolicy",[465,482,483],{"class":475},"\"\n",[447,485,486,488,489,491,492,495,496,500,501,504,505,508,509,512,513,516],{},[451,487,453],{}," is the ",[451,490,82],{}," module's per-request authorization subpackage. ",[493,494,93],"a",{"href":94}," is coarse-grained: it says whether the caller may perform the operation ",[497,498,499],"em",{},"in general",". A ",[502,503,453],"strong",{}," decides whether they may perform it ",[497,506,507],{},"on this particular request"," (are they the owner, are they in the same tenant, is the record in the right state). A role cannot express this: having the ",[451,510,511],{},"orders.view"," permission does not tell you whether they may view order ",[497,514,515],{},"42",".",[447,518,519],{},"The policy is attached to the operation by the TypeSpec contract, and the generated enforcer middleware enforces it: you cannot skip it, and a missing implementation does not let requests through: it errors (fail-closed).",[521,522,524],"h2",{"id":523},"a-policy-is-a-function","A policy is a function",[447,526,527,528,531],{},"A policy is a named ",[451,529,530],{},"Func",":",[456,533,535],{"className":458,"code":534,"language":460,"meta":461,"style":461},"type Func func(ctx context.Context, id *rbac.Identity, req any) error\n",[451,536,537],{"__ignoreMap":461},[465,538,539,542,545,548,552,555,557,560,563,566,569,572,574,577,579,582,585,588],{"class":467,"line":468},[465,540,541],{"class":475},"type",[465,543,544],{"class":479}," Func",[465,546,547],{"class":475}," func(",[465,549,551],{"class":550},"sHdIc","ctx",[465,553,554],{"class":479}," context",[465,556,516],{"class":475},[465,558,559],{"class":479},"Context",[465,561,562],{"class":475},",",[465,564,565],{"class":550}," id",[465,567,568],{"class":475}," *",[465,570,571],{"class":479},"rbac",[465,573,516],{"class":475},[465,575,576],{"class":479},"Identity",[465,578,562],{"class":475},[465,580,581],{"class":550}," req",[465,583,584],{"class":479}," any",[465,586,587],{"class":475},")",[465,589,591],{"class":590},"spNyl"," error\n",[447,593,594,595,600,601,604,605,608,609,612,613,616,617,620,621,624,625,628,629,632,633,636,637,608,640,643],{},"It receives the authenticated caller (",[493,596,597],{"href":94},[451,598,599],{},"*rbac.Identity",") and, as ",[451,602,603],{},"req",", the generated, ",[502,606,607],{},"fully-bound"," ",[451,610,611],{},"\u003COp>Request"," struct (body, query and path parameters together), which you unpack with a type assertion. It allows with ",[451,614,615],{},"nil"," and blocks with an error; ",[451,618,619],{},"policy.Deny(detail)"," returns a ",[451,622,623],{},"*policy.Denial",", a plain Go error for which ",[451,626,627],{},"errors.Is(err, rbac.ErrForbidden)"," is true and whose ",[451,630,631],{},"Detail"," is safe to show to the client. The kit's ",[451,634,635],{},"server.WriteError"," renders it as a ",[502,638,639],{},"403",[493,641,642],{"href":395},"problem"," with that message:",[456,645,647],{"className":458,"code":646,"language":460,"meta":461,"style":461},"func(ctx context.Context, id *rbac.Identity, req any) error {\n    r, ok := req.(shopapigen.GetOrderRequest)\n    if !ok {\n        return policy.Deny(\"unexpected request type\")\n    }\n    \u002F\u002F ... decide based on r and id\n    return nil \u002F\u002F allowed\n}\n",[451,648,649,694,725,740,769,775,782,794],{"__ignoreMap":461},[465,650,651,654,657,660,662,664,666,669,672,674,676,678,680,683,686,688,691],{"class":467,"line":468},[465,652,653],{"class":475},"func(",[465,655,656],{"class":550},"ctx ",[465,658,659],{"class":479},"context",[465,661,516],{"class":475},[465,663,559],{"class":479},[465,665,562],{"class":475},[465,667,668],{"class":550}," id ",[465,670,671],{"class":475},"*",[465,673,571],{"class":479},[465,675,516],{"class":475},[465,677,576],{"class":479},[465,679,562],{"class":475},[465,681,682],{"class":550}," req ",[465,684,685],{"class":479},"any",[465,687,587],{"class":475},[465,689,690],{"class":590}," error",[465,692,693],{"class":475}," {\n",[465,695,697,701,703,706,709,711,714,717,719,722],{"class":467,"line":696},2,[465,698,700],{"class":699},"sTEyZ","    r",[465,702,562],{"class":475},[465,704,705],{"class":699}," ok ",[465,707,708],{"class":475},":=",[465,710,581],{"class":699},[465,712,713],{"class":475},".(",[465,715,716],{"class":479},"shopapigen",[465,718,516],{"class":475},[465,720,721],{"class":479},"GetOrderRequest",[465,723,724],{"class":475},")\n",[465,726,728,731,734,737],{"class":467,"line":727},3,[465,729,730],{"class":471},"    if",[465,732,733],{"class":475}," !",[465,735,736],{"class":699},"ok ",[465,738,739],{"class":475},"{\n",[465,741,743,746,749,751,755,758,761,765,767],{"class":467,"line":742},4,[465,744,745],{"class":471},"        return",[465,747,748],{"class":699}," policy",[465,750,516],{"class":475},[465,752,754],{"class":753},"s2Zo4","Deny",[465,756,757],{"class":475},"(",[465,759,760],{"class":475},"\"",[465,762,764],{"class":763},"sfazB","unexpected request type",[465,766,760],{"class":475},[465,768,724],{"class":475},[465,770,772],{"class":467,"line":771},5,[465,773,774],{"class":475},"    }\n",[465,776,778],{"class":467,"line":777},6,[465,779,781],{"class":780},"sHwdD","    \u002F\u002F ... decide based on r and id\n",[465,783,785,788,791],{"class":467,"line":784},7,[465,786,787],{"class":471},"    return",[465,789,790],{"class":475}," nil",[465,792,793],{"class":780}," \u002F\u002F allowed\n",[465,795,797],{"class":467,"line":796},8,[465,798,799],{"class":475},"}\n",[447,801,802,803,531],{},"Policies live by name in a ",[451,804,805],{},"Registry",[456,807,809],{"className":458,"code":808,"language":460,"meta":461,"style":461},"reg := policy.NewRegistry()\nreg.Register(\"orders.view\", ordersViewFn) \u002F\u002F registering the same name twice: panic\nfn, ok := reg.Get(\"orders.view\")\n",[451,810,811,828,856],{"__ignoreMap":461},[465,812,813,816,818,820,822,825],{"class":467,"line":468},[465,814,815],{"class":699},"reg ",[465,817,708],{"class":475},[465,819,748],{"class":699},[465,821,516],{"class":475},[465,823,824],{"class":753},"NewRegistry",[465,826,827],{"class":475},"()\n",[465,829,830,833,835,838,840,842,844,846,848,851,853],{"class":467,"line":696},[465,831,832],{"class":699},"reg",[465,834,516],{"class":475},[465,836,837],{"class":753},"Register",[465,839,757],{"class":475},[465,841,760],{"class":475},[465,843,511],{"class":763},[465,845,760],{"class":475},[465,847,562],{"class":475},[465,849,850],{"class":699}," ordersViewFn",[465,852,587],{"class":475},[465,854,855],{"class":780}," \u002F\u002F registering the same name twice: panic\n",[465,857,858,861,863,865,867,870,872,875,877,879,881,883],{"class":467,"line":727},[465,859,860],{"class":699},"fn",[465,862,562],{"class":475},[465,864,705],{"class":699},[465,866,708],{"class":475},[465,868,869],{"class":699}," reg",[465,871,516],{"class":475},[465,873,874],{"class":753},"Get",[465,876,757],{"class":475},[465,878,760],{"class":475},[465,880,511],{"class":763},[465,882,760],{"class":475},[465,884,724],{"class":475},[447,886,887,608,889,892,893,895,896,898],{},[451,888,837],{},[502,890,891],{},"panics"," on a duplicate name: that is a wiring bug, not a runtime condition. ",[451,894,874],{}," is nil-receiver-safe: a nil ",[451,897,805],{}," reports every name as unknown, which with the semantics below gives a fail-closed default.",[521,900,902],{"id":901},"the-declarative-chain-from-contract-to-enforcer","The declarative chain: from contract to enforcer",[447,904,905],{},"You do not call the policy in the handler; you declare it in the contract, and it reaches the running middleware in four steps:",[907,908,909,932,945,958],"ol",{},[910,911,912,915,916,919,920,923,924,927,928,931],"li",{},[502,913,914],{},"TypeSpec",": you put the ",[451,917,918],{},"@permission(\"...\")"," \u002F ",[451,921,922],{},"@policy(\"...\")"," decorators on the operation. The decorators come with the project scaffold (",[451,925,926],{},"spec\u002Ftypespec\u002Flib\u002Fpolicy.tsp",", imported by ",[451,929,930],{},"main.tsp",").",[910,933,934,937,938,919,941,944],{},[502,935,936],{},"OpenAPI",": the emitter writes them into the spec as ",[451,939,940],{},"x-permission",[451,942,943],{},"x-policy"," extensions.",[910,946,947,950,951,919,954,957],{},[502,948,949],{},"Generated code",": the oapi-codegen templates record them per operation in the ",[451,952,953],{},"PermissionByOperation",[451,955,956],{},"PolicyByOperation"," maps.",[910,959,960,963,964,967,968,516],{},[502,961,962],{},"Runtime",": the ",[451,965,966],{},"policy.Enforcer"," strict middleware consults those maps and runs the named policy from the ",[451,969,805],{},[447,971,972,973,976],{},"There is one engine and one enforcer: ",[451,974,975],{},"Enforcer"," runs as chi\u002Fnet-http strict middleware, with no engine-specific variant to choose between.",[447,978,979,980,983],{},"The order operations of the shop's ",[451,981,982],{},"api"," surface are tagged like this:",[456,985,989],{"className":986,"code":987,"language":988,"meta":461,"style":461},"language-tsp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F spec\u002Ftypespec\u002Fmodules\u002Fshop\u002Fapi.tsp\n@post\n@route(\"\u002Forders\")\n@operationId(\"PlaceOrder\")\n@permission(\"orders.place\")   \u002F\u002F coarse filter: do they hold the right\n@policy(\"orders.place\")       \u002F\u002F fine filter: are they ordering on their own behalf\nplaceOrder(@body body: PlaceOrderInput): Order | Unauthorized | Forbidden;\n\n@get\n@route(\"\u002Forders\u002F{orderId}\")\n@operationId(\"GetOrder\")\n@policy(\"orders.view\")        \u002F\u002F owner or admin\ngetOrder(@path orderId: string): Order | NotFound | Unauthorized | Forbidden;\n","tsp",[451,990,991,996,1002,1014,1026,1041,1055,1069,1075,1081,1093,1105,1120],{"__ignoreMap":461},[465,992,993],{"class":467,"line":468},[465,994,995],{"class":780},"\u002F\u002F spec\u002Ftypespec\u002Fmodules\u002Fshop\u002Fapi.tsp\n",[465,997,998],{"class":467,"line":696},[465,999,1001],{"class":1000},"swJcz","@post\n",[465,1003,1004,1007,1009,1012],{"class":467,"line":727},[465,1005,1006],{"class":1000},"@route",[465,1008,757],{"class":475},[465,1010,1011],{"class":763},"\"\u002Forders\"",[465,1013,724],{"class":475},[465,1015,1016,1019,1021,1024],{"class":467,"line":742},[465,1017,1018],{"class":1000},"@operationId",[465,1020,757],{"class":475},[465,1022,1023],{"class":763},"\"PlaceOrder\"",[465,1025,724],{"class":475},[465,1027,1028,1031,1033,1036,1038],{"class":467,"line":771},[465,1029,1030],{"class":1000},"@permission",[465,1032,757],{"class":475},[465,1034,1035],{"class":763},"\"orders.place\"",[465,1037,587],{"class":475},[465,1039,1040],{"class":780},"   \u002F\u002F coarse filter: do they hold the right\n",[465,1042,1043,1046,1048,1050,1052],{"class":467,"line":777},[465,1044,1045],{"class":1000},"@policy",[465,1047,757],{"class":475},[465,1049,1035],{"class":763},[465,1051,587],{"class":475},[465,1053,1054],{"class":780},"       \u002F\u002F fine filter: are they ordering on their own behalf\n",[465,1056,1057,1060,1063,1066],{"class":467,"line":784},[465,1058,1059],{"class":699},"placeOrder(",[465,1061,1062],{"class":1000},"@body",[465,1064,1065],{"class":699}," body: PlaceOrderInput): Order | Unauthorized | Forbidden",[465,1067,1068],{"class":475},";\n",[465,1070,1071],{"class":467,"line":796},[465,1072,1074],{"emptyLinePlaceholder":1073},true,"\n",[465,1076,1078],{"class":467,"line":1077},9,[465,1079,1080],{"class":1000},"@get\n",[465,1082,1084,1086,1088,1091],{"class":467,"line":1083},10,[465,1085,1006],{"class":1000},[465,1087,757],{"class":475},[465,1089,1090],{"class":763},"\"\u002Forders\u002F{orderId}\"",[465,1092,724],{"class":475},[465,1094,1096,1098,1100,1103],{"class":467,"line":1095},11,[465,1097,1018],{"class":1000},[465,1099,757],{"class":475},[465,1101,1102],{"class":763},"\"GetOrder\"",[465,1104,724],{"class":475},[465,1106,1108,1110,1112,1115,1117],{"class":467,"line":1107},12,[465,1109,1045],{"class":1000},[465,1111,757],{"class":475},[465,1113,1114],{"class":763},"\"orders.view\"",[465,1116,587],{"class":475},[465,1118,1119],{"class":780},"        \u002F\u002F owner or admin\n",[465,1121,1123,1126,1129,1132],{"class":467,"line":1122},13,[465,1124,1125],{"class":699},"getOrder(",[465,1127,1128],{"class":1000},"@path",[465,1130,1131],{"class":699}," orderId: string): Order | NotFound | Unauthorized | Forbidden",[465,1133,1068],{"class":475},[447,1135,1136,1137,1140],{},"After ",[451,1138,1139],{},"mise run generate"," the maps appear in the generated package:",[456,1142,1144],{"className":458,"code":1143,"language":460,"meta":461,"style":461},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fhttp\u002Fgen (generated, do not edit)\nvar PermissionByOperation = map[string]string{\n    \"PlaceOrder\": \"orders.place\",\n}\n\nvar PolicyByOperation = map[string]string{\n    \"PlaceOrder\": \"orders.place\",\n    \"GetOrder\":   \"orders.view\",\n}\n",[451,1145,1146,1151,1175,1197,1201,1205,1224,1242,1262],{"__ignoreMap":461},[465,1147,1148],{"class":467,"line":468},[465,1149,1150],{"class":780},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fhttp\u002Fgen (generated, do not edit)\n",[465,1152,1153,1156,1159,1162,1165,1168,1171,1173],{"class":467,"line":696},[465,1154,1155],{"class":475},"var",[465,1157,1158],{"class":699}," PermissionByOperation ",[465,1160,1161],{"class":475},"=",[465,1163,1164],{"class":475}," map[",[465,1166,1167],{"class":590},"string",[465,1169,1170],{"class":475},"]",[465,1172,1167],{"class":590},[465,1174,739],{"class":475},[465,1176,1177,1180,1183,1185,1187,1189,1192,1194],{"class":467,"line":727},[465,1178,1179],{"class":475},"    \"",[465,1181,1182],{"class":763},"PlaceOrder",[465,1184,760],{"class":475},[465,1186,531],{"class":475},[465,1188,476],{"class":475},[465,1190,1191],{"class":763},"orders.place",[465,1193,760],{"class":475},[465,1195,1196],{"class":475},",\n",[465,1198,1199],{"class":467,"line":742},[465,1200,799],{"class":475},[465,1202,1203],{"class":467,"line":771},[465,1204,1074],{"emptyLinePlaceholder":1073},[465,1206,1207,1209,1212,1214,1216,1218,1220,1222],{"class":467,"line":777},[465,1208,1155],{"class":475},[465,1210,1211],{"class":699}," PolicyByOperation ",[465,1213,1161],{"class":475},[465,1215,1164],{"class":475},[465,1217,1167],{"class":590},[465,1219,1170],{"class":475},[465,1221,1167],{"class":590},[465,1223,739],{"class":475},[465,1225,1226,1228,1230,1232,1234,1236,1238,1240],{"class":467,"line":784},[465,1227,1179],{"class":475},[465,1229,1182],{"class":763},[465,1231,760],{"class":475},[465,1233,531],{"class":475},[465,1235,476],{"class":475},[465,1237,1191],{"class":763},[465,1239,760],{"class":475},[465,1241,1196],{"class":475},[465,1243,1244,1246,1249,1251,1253,1256,1258,1260],{"class":467,"line":796},[465,1245,1179],{"class":475},[465,1247,1248],{"class":763},"GetOrder",[465,1250,760],{"class":475},[465,1252,531],{"class":475},[465,1254,1255],{"class":475},"   \"",[465,1257,511],{"class":763},[465,1259,760],{"class":475},[465,1261,1196],{"class":475},[465,1263,1264],{"class":467,"line":1077},[465,1265,799],{"class":475},[447,1267,1268,1269,1271,1272,1275],{},"The enforcer wiring is generated by ",[493,1270,329],{"href":330}," into the ",[451,1273,1274],{},"Register\u003CSurface>"," function: there is nothing to wire by hand.",[456,1277,1280],{"className":458,"code":1278,"filename":1279,"language":460,"meta":461,"style":461},"func RegisterApi(router chi.Router, deps Dependencies) {\n    apiSvc := apiservice.New()\n    apiHandler := apihttp.New(apiSvc)\n    apiPolicies := apipolicy.New()\n    router.Route(\"\u002Fshop\", func(r chi.Router) {\n        shopapigen.HandlerWithOptions(shopapigen.NewStrictHandlerWithOptions(\n            apiHandler,\n            []shopapigen.StrictMiddlewareFunc{\n                server.StrictValidator[shopapigen.StrictHandlerFunc](nil),\n                \u002F\u002F Last = outermost: authorization runs before validation.\n                kitpolicy.Enforcer[shopapigen.StrictHandlerFunc](apiPolicies, shopapigen.PermissionByOperation, shopapigen.PolicyByOperation),\n            },\n            shopapigen.StrictHTTPServerOptions{\n                RequestErrorHandlerFunc:  httperr.WriteBadRequest,\n                ResponseErrorHandlerFunc: server.WriteError,\n            },\n        ), shopapigen.ChiServerOptions{\n            BaseRouter:       r,\n            ErrorHandlerFunc: httperr.WriteBadRequest,\n        })\n    })\n}\n","internal\u002Fmodules\u002Fshop\u002Fregister.go",[451,1281,1282,1315,1332,1353,1369,1405,1427,1434,1448,1471,1476,1519,1524,1536,1554,1572,1577,1592,1605,1622,1628,1634],{"__ignoreMap":461},[465,1283,1284,1287,1290,1292,1295,1298,1300,1303,1305,1308,1311,1313],{"class":467,"line":468},[465,1285,1286],{"class":475},"func",[465,1288,1289],{"class":753}," RegisterApi",[465,1291,757],{"class":475},[465,1293,1294],{"class":550},"router",[465,1296,1297],{"class":479}," chi",[465,1299,516],{"class":475},[465,1301,1302],{"class":479},"Router",[465,1304,562],{"class":475},[465,1306,1307],{"class":550}," deps",[465,1309,1310],{"class":479}," Dependencies",[465,1312,587],{"class":475},[465,1314,693],{"class":475},[465,1316,1317,1320,1322,1325,1327,1330],{"class":467,"line":696},[465,1318,1319],{"class":699},"    apiSvc ",[465,1321,708],{"class":475},[465,1323,1324],{"class":699}," apiservice",[465,1326,516],{"class":475},[465,1328,1329],{"class":753},"New",[465,1331,827],{"class":475},[465,1333,1334,1337,1339,1342,1344,1346,1348,1351],{"class":467,"line":727},[465,1335,1336],{"class":699},"    apiHandler ",[465,1338,708],{"class":475},[465,1340,1341],{"class":699}," apihttp",[465,1343,516],{"class":475},[465,1345,1329],{"class":753},[465,1347,757],{"class":475},[465,1349,1350],{"class":699},"apiSvc",[465,1352,724],{"class":475},[465,1354,1355,1358,1360,1363,1365,1367],{"class":467,"line":742},[465,1356,1357],{"class":699},"    apiPolicies ",[465,1359,708],{"class":475},[465,1361,1362],{"class":699}," apipolicy",[465,1364,516],{"class":475},[465,1366,1329],{"class":753},[465,1368,827],{"class":475},[465,1370,1371,1374,1376,1379,1381,1383,1386,1388,1390,1392,1395,1397,1399,1401,1403],{"class":467,"line":771},[465,1372,1373],{"class":699},"    router",[465,1375,516],{"class":475},[465,1377,1378],{"class":753},"Route",[465,1380,757],{"class":475},[465,1382,760],{"class":475},[465,1384,1385],{"class":763},"\u002Fshop",[465,1387,760],{"class":475},[465,1389,562],{"class":475},[465,1391,547],{"class":475},[465,1393,1394],{"class":550},"r",[465,1396,1297],{"class":479},[465,1398,516],{"class":475},[465,1400,1302],{"class":479},[465,1402,587],{"class":475},[465,1404,693],{"class":475},[465,1406,1407,1410,1412,1415,1417,1419,1421,1424],{"class":467,"line":777},[465,1408,1409],{"class":699},"        shopapigen",[465,1411,516],{"class":475},[465,1413,1414],{"class":753},"HandlerWithOptions",[465,1416,757],{"class":475},[465,1418,716],{"class":699},[465,1420,516],{"class":475},[465,1422,1423],{"class":753},"NewStrictHandlerWithOptions",[465,1425,1426],{"class":475},"(\n",[465,1428,1429,1432],{"class":467,"line":784},[465,1430,1431],{"class":699},"            apiHandler",[465,1433,1196],{"class":475},[465,1435,1436,1439,1441,1443,1446],{"class":467,"line":796},[465,1437,1438],{"class":475},"            []",[465,1440,716],{"class":479},[465,1442,516],{"class":475},[465,1444,1445],{"class":479},"StrictMiddlewareFunc",[465,1447,739],{"class":475},[465,1449,1450,1453,1455,1458,1461,1463,1465,1468],{"class":467,"line":1077},[465,1451,1452],{"class":699},"                server",[465,1454,516],{"class":475},[465,1456,1457],{"class":753},"StrictValidator",[465,1459,1460],{"class":475},"[",[465,1462,716],{"class":479},[465,1464,516],{"class":475},[465,1466,1467],{"class":479},"StrictHandlerFunc",[465,1469,1470],{"class":475},"](nil),\n",[465,1472,1473],{"class":467,"line":1083},[465,1474,1475],{"class":780},"                \u002F\u002F Last = outermost: authorization runs before validation.\n",[465,1477,1478,1481,1483,1485,1487,1489,1491,1493,1496,1499,1501,1504,1506,1508,1510,1512,1514,1516],{"class":467,"line":1095},[465,1479,1480],{"class":699},"                kitpolicy",[465,1482,516],{"class":475},[465,1484,975],{"class":753},[465,1486,1460],{"class":475},[465,1488,716],{"class":479},[465,1490,516],{"class":475},[465,1492,1467],{"class":479},[465,1494,1495],{"class":475},"](",[465,1497,1498],{"class":699},"apiPolicies",[465,1500,562],{"class":475},[465,1502,1503],{"class":699}," shopapigen",[465,1505,516],{"class":475},[465,1507,953],{"class":699},[465,1509,562],{"class":475},[465,1511,1503],{"class":699},[465,1513,516],{"class":475},[465,1515,956],{"class":699},[465,1517,1518],{"class":475},"),\n",[465,1520,1521],{"class":467,"line":1107},[465,1522,1523],{"class":475},"            },\n",[465,1525,1526,1529,1531,1534],{"class":467,"line":1122},[465,1527,1528],{"class":479},"            shopapigen",[465,1530,516],{"class":475},[465,1532,1533],{"class":479},"StrictHTTPServerOptions",[465,1535,739],{"class":475},[465,1537,1539,1542,1544,1547,1549,1552],{"class":467,"line":1538},14,[465,1540,1541],{"class":699},"                RequestErrorHandlerFunc",[465,1543,531],{"class":475},[465,1545,1546],{"class":699},"  httperr",[465,1548,516],{"class":475},[465,1550,1551],{"class":699},"WriteBadRequest",[465,1553,1196],{"class":475},[465,1555,1557,1560,1562,1565,1567,1570],{"class":467,"line":1556},15,[465,1558,1559],{"class":699},"                ResponseErrorHandlerFunc",[465,1561,531],{"class":475},[465,1563,1564],{"class":699}," server",[465,1566,516],{"class":475},[465,1568,1569],{"class":699},"WriteError",[465,1571,1196],{"class":475},[465,1573,1575],{"class":467,"line":1574},16,[465,1576,1523],{"class":475},[465,1578,1580,1583,1585,1587,1590],{"class":467,"line":1579},17,[465,1581,1582],{"class":475},"        ),",[465,1584,1503],{"class":479},[465,1586,516],{"class":475},[465,1588,1589],{"class":479},"ChiServerOptions",[465,1591,739],{"class":475},[465,1593,1595,1598,1600,1603],{"class":467,"line":1594},18,[465,1596,1597],{"class":699},"            BaseRouter",[465,1599,531],{"class":475},[465,1601,1602],{"class":699},"       r",[465,1604,1196],{"class":475},[465,1606,1608,1611,1613,1616,1618,1620],{"class":467,"line":1607},19,[465,1609,1610],{"class":699},"            ErrorHandlerFunc",[465,1612,531],{"class":475},[465,1614,1615],{"class":699}," httperr",[465,1617,516],{"class":475},[465,1619,1551],{"class":699},[465,1621,1196],{"class":475},[465,1623,1625],{"class":467,"line":1624},20,[465,1626,1627],{"class":475},"        })\n",[465,1629,1631],{"class":467,"line":1630},21,[465,1632,1633],{"class":475},"    })\n",[465,1635,1637],{"class":467,"line":1636},22,[465,1638,799],{"class":475},[447,1640,757,1641,1644,1645,1647,1648,1651,1652,1654],{},[451,1642,1643],{},"kitpolicy"," here is the alias for ",[451,1646,480],{},": the generated ",[451,1649,1650],{},"policy\u002F"," package inside the surface is itself named ",[451,1653,453],{},", so the module import needs a different local name to avoid a collision.)",[447,1656,1657,1658,1661,1662,1665,1666,1668],{},"In the strict-middleware slice the ",[502,1659,1660],{},"last element is outermost",", so the enforcer runs before the validators: the 401\u002F403 precedes body ",[493,1663,1664],{"href":400},"validation",", meaning a policy sees a decoded but not-yet-validated body. (The generic type parameter exists because oapi-codegen defines the ",[451,1667,1467],{}," type per generated package.)",[1670,1671,1672,1673,1676,1677,1680,1681,1684,1685,1687,1688,1691],"note",{},"The enforcer only ",[497,1674,1675],{},"checks"," the identity: ",[497,1678,1679],{},"producing"," it from the Bearer token is the ",[493,1682,1683],{"href":89},"auth middleware","'s job. For a mixed surface (public + protected operations, like the shop's ",[451,1686,982],{},"), make token parsing conditional; the pattern is shown on the ",[493,1689,1690],{"href":89},"authentication"," page; tagged operations without an identity get their 401 here, from the enforcer.",[521,1693,1695],{"id":1694},"semantics-precisely","Semantics, precisely",[447,1697,1698],{},"Per operation, the enforcer decides as follows:",[1700,1701,1702,1715],"table",{},[1703,1704,1705],"thead",{},[1706,1707,1708,1712],"tr",{},[1709,1710,1711],"th",{},"Situation",[1709,1713,1714],{},"Result",[1716,1717,1718,1732,1745,1759,1776],"tbody",{},[1706,1719,1720,1729],{},[1721,1722,1723,1724,1726,1727],"td",{},"the operation has neither ",[451,1725,1030],{}," nor ",[451,1728,1045],{},[1721,1730,1731],{},"pass, no identity needed",[1706,1733,1734,1737],{},[1721,1735,1736],{},"tagged operation, no identity in the context",[1721,1738,1739,608,1742],{},[502,1740,1741],{},"401",[451,1743,1744],{},"authentication required",[1706,1746,1747,1752],{},[1721,1748,1749,1751],{},[451,1750,1030],{}," present but not held by the user",[1721,1753,1754,608,1756],{},[502,1755,639],{},[451,1757,1758],{},"insufficient privileges",[1706,1760,1761,1767],{},[1721,1762,1763,1764,587],{},"the policy returns an error (typically ",[451,1765,1766],{},"policy.Deny(...)",[1721,1768,1769,1770,1772,1773,1775],{},"that error goes out (",[451,1771,754],{}," is ",[502,1774,639],{},", with the detail message)",[1706,1777,1778,1784],{},[1721,1779,1780,1781,1783],{},"the ",[451,1782,1045],{}," name is not registered in the Registry",[1721,1785,1786,1789,1790,1793],{},[502,1787,1788],{},"fail-closed",": plain error → ",[502,1791,1792],{},"500"," (server misconfiguration, not a client error)",[447,1795,1796,1797,1800,1801,1804],{},"When both tags are on the operation, the ",[502,1798,1799],{},"permission runs first",": the policy only runs once the right is held. An unregistered policy is not a 403: that is not the caller's fault but yours, and you want to notice it as a 500 (with a stack, with a ",[493,1802,1803],{"href":194},"Sentry alert","), not as a silent denial.",[521,1806,1808],{"id":1807},"the-shops-orderpolicy-end-to-end","The shop's OrderPolicy, end to end",[447,1810,1811,1813,1814,1816,1817,1819,1820,1823,1824,1826,1827,1829],{},[451,1812,329],{}," scaffolds a ",[451,1815,1650],{}," package with a registry stub for every surface: you only write the ",[451,1818,837],{}," calls at the ",[451,1821,1822],{},"gpsystem:policies"," anchor. The shop's ",[451,1825,511],{}," policy is the owner-or-admin rule; ",[451,1828,1191],{}," rules out placing an order on someone else's behalf:",[456,1831,1833],{"className":458,"code":1832,"language":460,"meta":461,"style":461},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fpolicy\u002Fpolicy.go\npackage policy\n\nimport (\n    \"context\"\n\n    kitpolicy \"github.com\u002Fgp-system\u002Fauth\u002Fpolicy\"\n    \"github.com\u002Fgp-system\u002Fauth\u002Frbac\"\n\n    gen \"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fhttp\u002Fgen\"\n    \"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop\u002Frepository\"\n)\n\nfunc New(orders *repository.OrderRepo) *kitpolicy.Registry {\n    reg := kitpolicy.NewRegistry()\n    \u002F\u002F gpsystem:policies\n    reg.Register(\"orders.view\", func(ctx context.Context, id *rbac.Identity, req any) error {\n        if id.HasRole(\"admin\") {\n            return nil\n        }\n        r, ok := req.(gen.GetOrderRequest)\n        if !ok {\n            return kitpolicy.Deny(\"unexpected request type\")\n        }\n        order, err := orders.GetByID(ctx, r.OrderId) \u002F\u002F DB access via closure\n        if err != nil {\n            return err\n        }\n        if order.UserID != id.Subject {\n            return kitpolicy.Deny(\"You can only view your own orders.\")\n        }\n        return nil\n    })\n    reg.Register(\"orders.place\", func(ctx context.Context, id *rbac.Identity, req any) error {\n        r, ok := req.(gen.PlaceOrderRequest)\n        if !ok {\n            return kitpolicy.Deny(\"unexpected request type\")\n        }\n        if r.Body.CustomerId != id.Subject {\n            return kitpolicy.Deny(\"You can only place orders on your own behalf.\")\n        }\n        return nil\n    })\n    return reg\n}\n",[451,1834,1835,1840,1848,1852,1859,1867,1871,1882,1891,1895,1907,1916,1920,1924,1958,1974,1979,2032,2057,2065,2070,2094,2104,2125,2130,2170,2184,2192,2197,2221,2243,2248,2255,2260,2313,2337,2348,2369,2374,2401,2423,2428,2435,2440,2448],{"__ignoreMap":461},[465,1836,1837],{"class":467,"line":468},[465,1838,1839],{"class":780},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fpolicy\u002Fpolicy.go\n",[465,1841,1842,1845],{"class":467,"line":696},[465,1843,1844],{"class":475},"package",[465,1846,1847],{"class":479}," policy\n",[465,1849,1850],{"class":467,"line":727},[465,1851,1074],{"emptyLinePlaceholder":1073},[465,1853,1854,1856],{"class":467,"line":742},[465,1855,472],{"class":471},[465,1857,1858],{"class":475}," (\n",[465,1860,1861,1863,1865],{"class":467,"line":771},[465,1862,1179],{"class":475},[465,1864,659],{"class":479},[465,1866,483],{"class":475},[465,1868,1869],{"class":467,"line":777},[465,1870,1074],{"emptyLinePlaceholder":1073},[465,1872,1873,1876,1878,1880],{"class":467,"line":784},[465,1874,1875],{"class":699},"    kitpolicy ",[465,1877,760],{"class":475},[465,1879,480],{"class":479},[465,1881,483],{"class":475},[465,1883,1884,1886,1889],{"class":467,"line":796},[465,1885,1179],{"class":475},[465,1887,1888],{"class":479},"github.com\u002Fgp-system\u002Fauth\u002Frbac",[465,1890,483],{"class":475},[465,1892,1893],{"class":467,"line":1077},[465,1894,1074],{"emptyLinePlaceholder":1073},[465,1896,1897,1900,1902,1905],{"class":467,"line":1083},[465,1898,1899],{"class":699},"    gen ",[465,1901,760],{"class":475},[465,1903,1904],{"class":479},"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fhttp\u002Fgen",[465,1906,483],{"class":475},[465,1908,1909,1911,1914],{"class":467,"line":1095},[465,1910,1179],{"class":475},[465,1912,1913],{"class":479},"github.com\u002Facme\u002Fshop\u002Finternal\u002Fmodules\u002Fshop\u002Frepository",[465,1915,483],{"class":475},[465,1917,1918],{"class":467,"line":1107},[465,1919,724],{"class":475},[465,1921,1922],{"class":467,"line":1122},[465,1923,1074],{"emptyLinePlaceholder":1073},[465,1925,1926,1928,1931,1933,1936,1938,1941,1943,1946,1948,1950,1952,1954,1956],{"class":467,"line":1538},[465,1927,1286],{"class":475},[465,1929,1930],{"class":753}," New",[465,1932,757],{"class":475},[465,1934,1935],{"class":550},"orders",[465,1937,568],{"class":475},[465,1939,1940],{"class":479},"repository",[465,1942,516],{"class":475},[465,1944,1945],{"class":479},"OrderRepo",[465,1947,587],{"class":475},[465,1949,568],{"class":475},[465,1951,1643],{"class":479},[465,1953,516],{"class":475},[465,1955,805],{"class":479},[465,1957,693],{"class":475},[465,1959,1960,1963,1965,1968,1970,1972],{"class":467,"line":1556},[465,1961,1962],{"class":699},"    reg ",[465,1964,708],{"class":475},[465,1966,1967],{"class":699}," kitpolicy",[465,1969,516],{"class":475},[465,1971,824],{"class":753},[465,1973,827],{"class":475},[465,1975,1976],{"class":467,"line":1574},[465,1977,1978],{"class":780},"    \u002F\u002F gpsystem:policies\n",[465,1980,1981,1984,1986,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030],{"class":467,"line":1579},[465,1982,1983],{"class":699},"    reg",[465,1985,516],{"class":475},[465,1987,837],{"class":753},[465,1989,757],{"class":475},[465,1991,760],{"class":475},[465,1993,511],{"class":763},[465,1995,760],{"class":475},[465,1997,562],{"class":475},[465,1999,547],{"class":475},[465,2001,551],{"class":550},[465,2003,554],{"class":479},[465,2005,516],{"class":475},[465,2007,559],{"class":479},[465,2009,562],{"class":475},[465,2011,565],{"class":550},[465,2013,568],{"class":475},[465,2015,571],{"class":479},[465,2017,516],{"class":475},[465,2019,576],{"class":479},[465,2021,562],{"class":475},[465,2023,581],{"class":550},[465,2025,584],{"class":479},[465,2027,587],{"class":475},[465,2029,690],{"class":590},[465,2031,693],{"class":475},[465,2033,2034,2037,2039,2041,2044,2046,2048,2051,2053,2055],{"class":467,"line":1594},[465,2035,2036],{"class":471},"        if",[465,2038,565],{"class":699},[465,2040,516],{"class":475},[465,2042,2043],{"class":753},"HasRole",[465,2045,757],{"class":475},[465,2047,760],{"class":475},[465,2049,2050],{"class":763},"admin",[465,2052,760],{"class":475},[465,2054,587],{"class":475},[465,2056,693],{"class":475},[465,2058,2059,2062],{"class":467,"line":1607},[465,2060,2061],{"class":471},"            return",[465,2063,2064],{"class":475}," nil\n",[465,2066,2067],{"class":467,"line":1624},[465,2068,2069],{"class":475},"        }\n",[465,2071,2072,2075,2077,2079,2081,2083,2085,2088,2090,2092],{"class":467,"line":1630},[465,2073,2074],{"class":699},"        r",[465,2076,562],{"class":475},[465,2078,705],{"class":699},[465,2080,708],{"class":475},[465,2082,581],{"class":699},[465,2084,713],{"class":475},[465,2086,2087],{"class":479},"gen",[465,2089,516],{"class":475},[465,2091,721],{"class":479},[465,2093,724],{"class":475},[465,2095,2096,2098,2100,2102],{"class":467,"line":1636},[465,2097,2036],{"class":471},[465,2099,733],{"class":475},[465,2101,736],{"class":699},[465,2103,739],{"class":475},[465,2105,2107,2109,2111,2113,2115,2117,2119,2121,2123],{"class":467,"line":2106},23,[465,2108,2061],{"class":471},[465,2110,1967],{"class":699},[465,2112,516],{"class":475},[465,2114,754],{"class":753},[465,2116,757],{"class":475},[465,2118,760],{"class":475},[465,2120,764],{"class":763},[465,2122,760],{"class":475},[465,2124,724],{"class":475},[465,2126,2128],{"class":467,"line":2127},24,[465,2129,2069],{"class":475},[465,2131,2133,2136,2138,2141,2143,2146,2148,2151,2153,2155,2157,2160,2162,2165,2167],{"class":467,"line":2132},25,[465,2134,2135],{"class":699},"        order",[465,2137,562],{"class":475},[465,2139,2140],{"class":699}," err ",[465,2142,708],{"class":475},[465,2144,2145],{"class":699}," orders",[465,2147,516],{"class":475},[465,2149,2150],{"class":753},"GetByID",[465,2152,757],{"class":475},[465,2154,551],{"class":699},[465,2156,562],{"class":475},[465,2158,2159],{"class":699}," r",[465,2161,516],{"class":475},[465,2163,2164],{"class":699},"OrderId",[465,2166,587],{"class":475},[465,2168,2169],{"class":780}," \u002F\u002F DB access via closure\n",[465,2171,2173,2175,2177,2180,2182],{"class":467,"line":2172},26,[465,2174,2036],{"class":471},[465,2176,2140],{"class":699},[465,2178,2179],{"class":475},"!=",[465,2181,790],{"class":475},[465,2183,693],{"class":475},[465,2185,2187,2189],{"class":467,"line":2186},27,[465,2188,2061],{"class":471},[465,2190,2191],{"class":699}," err\n",[465,2193,2195],{"class":467,"line":2194},28,[465,2196,2069],{"class":475},[465,2198,2200,2202,2205,2207,2210,2212,2214,2216,2219],{"class":467,"line":2199},29,[465,2201,2036],{"class":471},[465,2203,2204],{"class":699}," order",[465,2206,516],{"class":475},[465,2208,2209],{"class":699},"UserID ",[465,2211,2179],{"class":475},[465,2213,565],{"class":699},[465,2215,516],{"class":475},[465,2217,2218],{"class":699},"Subject ",[465,2220,739],{"class":475},[465,2222,2224,2226,2228,2230,2232,2234,2236,2239,2241],{"class":467,"line":2223},30,[465,2225,2061],{"class":471},[465,2227,1967],{"class":699},[465,2229,516],{"class":475},[465,2231,754],{"class":753},[465,2233,757],{"class":475},[465,2235,760],{"class":475},[465,2237,2238],{"class":763},"You can only view your own orders.",[465,2240,760],{"class":475},[465,2242,724],{"class":475},[465,2244,2246],{"class":467,"line":2245},31,[465,2247,2069],{"class":475},[465,2249,2251,2253],{"class":467,"line":2250},32,[465,2252,745],{"class":471},[465,2254,2064],{"class":475},[465,2256,2258],{"class":467,"line":2257},33,[465,2259,1633],{"class":475},[465,2261,2263,2265,2267,2269,2271,2273,2275,2277,2279,2281,2283,2285,2287,2289,2291,2293,2295,2297,2299,2301,2303,2305,2307,2309,2311],{"class":467,"line":2262},34,[465,2264,1983],{"class":699},[465,2266,516],{"class":475},[465,2268,837],{"class":753},[465,2270,757],{"class":475},[465,2272,760],{"class":475},[465,2274,1191],{"class":763},[465,2276,760],{"class":475},[465,2278,562],{"class":475},[465,2280,547],{"class":475},[465,2282,551],{"class":550},[465,2284,554],{"class":479},[465,2286,516],{"class":475},[465,2288,559],{"class":479},[465,2290,562],{"class":475},[465,2292,565],{"class":550},[465,2294,568],{"class":475},[465,2296,571],{"class":479},[465,2298,516],{"class":475},[465,2300,576],{"class":479},[465,2302,562],{"class":475},[465,2304,581],{"class":550},[465,2306,584],{"class":479},[465,2308,587],{"class":475},[465,2310,690],{"class":590},[465,2312,693],{"class":475},[465,2314,2316,2318,2320,2322,2324,2326,2328,2330,2332,2335],{"class":467,"line":2315},35,[465,2317,2074],{"class":699},[465,2319,562],{"class":475},[465,2321,705],{"class":699},[465,2323,708],{"class":475},[465,2325,581],{"class":699},[465,2327,713],{"class":475},[465,2329,2087],{"class":479},[465,2331,516],{"class":475},[465,2333,2334],{"class":479},"PlaceOrderRequest",[465,2336,724],{"class":475},[465,2338,2340,2342,2344,2346],{"class":467,"line":2339},36,[465,2341,2036],{"class":471},[465,2343,733],{"class":475},[465,2345,736],{"class":699},[465,2347,739],{"class":475},[465,2349,2351,2353,2355,2357,2359,2361,2363,2365,2367],{"class":467,"line":2350},37,[465,2352,2061],{"class":471},[465,2354,1967],{"class":699},[465,2356,516],{"class":475},[465,2358,754],{"class":753},[465,2360,757],{"class":475},[465,2362,760],{"class":475},[465,2364,764],{"class":763},[465,2366,760],{"class":475},[465,2368,724],{"class":475},[465,2370,2372],{"class":467,"line":2371},38,[465,2373,2069],{"class":475},[465,2375,2377,2379,2381,2383,2386,2388,2391,2393,2395,2397,2399],{"class":467,"line":2376},39,[465,2378,2036],{"class":471},[465,2380,2159],{"class":699},[465,2382,516],{"class":475},[465,2384,2385],{"class":699},"Body",[465,2387,516],{"class":475},[465,2389,2390],{"class":699},"CustomerId ",[465,2392,2179],{"class":475},[465,2394,565],{"class":699},[465,2396,516],{"class":475},[465,2398,2218],{"class":699},[465,2400,739],{"class":475},[465,2402,2404,2406,2408,2410,2412,2414,2416,2419,2421],{"class":467,"line":2403},40,[465,2405,2061],{"class":471},[465,2407,1967],{"class":699},[465,2409,516],{"class":475},[465,2411,754],{"class":753},[465,2413,757],{"class":475},[465,2415,760],{"class":475},[465,2417,2418],{"class":763},"You can only place orders on your own behalf.",[465,2420,760],{"class":475},[465,2422,724],{"class":475},[465,2424,2426],{"class":467,"line":2425},41,[465,2427,2069],{"class":475},[465,2429,2431,2433],{"class":467,"line":2430},42,[465,2432,745],{"class":471},[465,2434,2064],{"class":475},[465,2436,2438],{"class":467,"line":2437},43,[465,2439,1633],{"class":475},[465,2441,2443,2445],{"class":467,"line":2442},44,[465,2444,787],{"class":471},[465,2446,2447],{"class":699}," reg\n",[465,2449,2451],{"class":467,"line":2450},45,[465,2452,799],{"class":475},[447,2454,2455,2456,2459,2460,2463,2464,2467,2468,2470],{},"The generated stub's ",[451,2457,2458],{},"New()"," takes no parameters; when a policy needs a repository (as here), extend the signature and adjust its single call site in ",[451,2461,2462],{},"register.go"," (",[451,2465,2466],{},"apipolicy.New(orderRepo)","). The ",[451,2469,1274],{}," function is yours, the generator only wrote it at creation time.",[447,2472,2473,2474,2477],{},"This is how a ",[451,2475,2476],{},"GET \u002Fapi\u002Fv1\u002Fshop\u002Forders\u002F42"," plays out in each case:",[1700,2479,2480,2493],{},[1703,2481,2482],{},[1706,2483,2484,2487,2490],{},[1709,2485,2486],{},"Caller",[1709,2488,2489],{},"Path taken",[1709,2491,2492],{},"Response",[1716,2494,2495,2508,2530,2549,2569],{},[1706,2496,2497,2500,2503],{},[1721,2498,2499],{},"without a token",[1721,2501,2502],{},"tagged operation, no identity",[1721,2504,2505,2507],{},[502,2506,1741],{}," problem",[1706,2509,2510,2513,2524],{},[1721,2511,2512],{},"the owner of order 42",[1721,2514,2515,2517,2518,2520,2521],{},[451,2516,511],{}," → not admin → ",[451,2519,2150],{}," → ",[451,2522,2523],{},"UserID == Subject",[1721,2525,2526,2529],{},[502,2527,2528],{},"200",", the handler runs",[1706,2531,2532,2535,2543],{},[1721,2533,2534],{},"another logged-in user",[1721,2536,2537,2538,2520,2541],{},"same, but ",[451,2539,2540],{},"UserID != Subject",[451,2542,754],{},[1721,2544,2545,608,2547],{},[502,2546,639],{},[451,2548,2238],{},[1706,2550,2551,2557,2565],{},[1721,2552,2553,2554,2556],{},"a user with the ",[451,2555,2050],{}," role",[1721,2558,2559,2520,2561,2564],{},[451,2560,511],{},[451,2562,2563],{},"HasRole(\"admin\")"," → immediate allow",[1721,2566,2567,2529],{},[502,2568,2528],{},[1706,2570,2571,2577,2580],{},[1721,2572,2573,2574,2576],{},"anyone, if ",[451,2575,511],{}," is not registered",[1721,2578,2579],{},"fail-closed error",[1721,2581,2582,2584,2585],{},[502,2583,1792],{}," + stack, ",[451,2586,2587],{},"policy \"orders.view\" ... is not registered",[447,2589,2590,2591,2593,2594,608,2596,2599,2600,608,2602,516],{},"For ",[451,2592,1182],{}," the same chain is one step longer: first the ",[451,2595,1191],{},[502,2597,2598],{},"permission"," (not held → 403), and only then the ",[451,2601,1191],{},[502,2603,453],{},[521,2605,2607],{"id":2606},"a-new-operation-with-a-policy","A new operation with a policy",[447,2609,2610,2611,2613,2614,919,2617,2620],{},"The ",[493,2612,338],{"href":339}," command generates an already-tagged operation into the contract via its ",[451,2615,2616],{},"--permission",[451,2618,2619],{},"--policy"," flags, and reminds you to register:",[456,2622,2626],{"className":2623,"code":2624,"language":2625,"meta":461,"style":461},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go tool gpsystem add handler shop api getOrder --method get --path \"\u002Forders\u002F{orderId}\" \\\n  --policy \"orders.view\"\n# ...\n#   # register policy \"orders.view\" in internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fpolicy\u002Fpolicy.go\n","sh",[451,2627,2628,2672,2683,2688],{"__ignoreMap":461},[465,2629,2630,2632,2635,2638,2641,2644,2647,2650,2653,2656,2659,2662,2664,2667,2669],{"class":467,"line":468},[465,2631,460],{"class":479},[465,2633,2634],{"class":763}," tool",[465,2636,2637],{"class":763}," gpsystem",[465,2639,2640],{"class":763}," add",[465,2642,2643],{"class":763}," handler",[465,2645,2646],{"class":763}," shop",[465,2648,2649],{"class":763}," api",[465,2651,2652],{"class":763}," getOrder",[465,2654,2655],{"class":763}," --method",[465,2657,2658],{"class":763}," get",[465,2660,2661],{"class":763}," --path",[465,2663,476],{"class":475},[465,2665,2666],{"class":763},"\u002Forders\u002F{orderId}",[465,2668,760],{"class":475},[465,2670,2671],{"class":699}," \\\n",[465,2673,2674,2677,2679,2681],{"class":467,"line":696},[465,2675,2676],{"class":763},"  --policy",[465,2678,476],{"class":475},[465,2680,511],{"class":763},[465,2682,483],{"class":475},[465,2684,2685],{"class":467,"line":727},[465,2686,2687],{"class":780},"# ...\n",[465,2689,2690],{"class":467,"line":742},[465,2691,2692],{"class":780},"#   # register policy \"orders.view\" in internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fpolicy\u002Fpolicy.go\n",[447,2694,2695,2696,2698,2699,2701,2702,2705,2706,2709],{},"The rest of the chain: ",[493,2697,1690],{"href":89}," (where the identity comes from), ",[493,2700,93],{"href":94}," (role and permission checks), and the ",[493,2703,2704],{"href":234},"codegen pipeline"," (how ",[451,2707,2708],{},".tsp"," becomes running code).",[2711,2712,2713],"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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":461,"searchDepth":696,"depth":696,"links":2715},[2716,2717,2718,2719,2720],{"id":523,"depth":696,"text":524},{"id":901,"depth":696,"text":902},{"id":1694,"depth":696,"text":1695},{"id":1807,"depth":696,"text":1808},{"id":2606,"depth":696,"text":2607},"Per-request, per-user authorization above roles: declared in the contract, enforced from generated code.","md",null,{},{"icon":101},{"title":98,"description":2721},"k8_ruLCCNpDKhulBaAop8IdHh26AouAopPxJ7WI4y3E",[2729,2731],{"title":93,"path":94,"stem":95,"description":2730,"icon":96,"children":-1},"Role and permission checks over the Identity carried in the context: with route guards and service-layer queries.",{"title":103,"path":104,"stem":105,"description":2732,"icon":106,"children":-1},"A complete, runnable example with no gp-system dependency beyond auth itself: HTTP and non-HTTP.",1785445880348]