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