[{"data":1,"prerenderedAt":1403},["ShallowReactive",2],{"navigation_docs":3,"-platform-reference-project-publishing-design":213,"-platform-reference-project-publishing-design-surround":1400},[4,142],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":36},"Kinotic Apps","i-lucide-rocket","\u002Fapps","01.apps",[10,14,18,37,58,91,102,122,127],{"title":11,"path":12,"stem":13},"Introduction","\u002Fapps\u002Fintroduction","01.apps\u002F01.introduction",{"title":15,"path":16,"stem":17},"Quick Start","\u002Fapps\u002Fquick-start","01.apps\u002F02.quick-start",{"title":19,"icon":20,"path":21,"stem":22,"children":23,"page":36},"Application Structure","i-lucide-folder-tree","\u002Fapps\u002Fapplication-structure","01.apps\u002F03.application-structure",[24,28,32],{"title":25,"path":26,"stem":27},"Overview","\u002Fapps\u002Fapplication-structure\u002Foverview","01.apps\u002F03.application-structure\u002F01.overview",{"title":29,"path":30,"stem":31},"Applications and Projects","\u002Fapps\u002Fapplication-structure\u002Fapplications-and-projects","01.apps\u002F03.application-structure\u002F02.applications-and-projects",{"title":33,"path":34,"stem":35},"Artifact Types","\u002Fapps\u002Fapplication-structure\u002Fartifact-types","01.apps\u002F03.application-structure\u002F03.artifact-types",false,{"title":38,"icon":39,"path":40,"stem":41,"children":42,"page":36},"Services","i-lucide-network","\u002Fapps\u002Fservices","01.apps\u002F04.services",[43,46,50,54],{"title":25,"path":44,"stem":45},"\u002Fapps\u002Fservices\u002Foverview","01.apps\u002F04.services\u002F01.overview",{"title":47,"path":48,"stem":49},"Publishing Services","\u002Fapps\u002Fservices\u002Fpublishing-services","01.apps\u002F04.services\u002F02.publishing-services",{"title":51,"path":52,"stem":53},"Service Proxies","\u002Fapps\u002Fservices\u002Fservice-proxies","01.apps\u002F04.services\u002F03.service-proxies",{"title":55,"path":56,"stem":57},"Streaming","\u002Fapps\u002Fservices\u002Fstreaming","01.apps\u002F04.services\u002F04.streaming",{"title":59,"icon":60,"path":61,"stem":62,"children":63,"page":36},"Persistence","i-lucide-database","\u002Fapps\u002Fpersistence","01.apps\u002F05.persistence",[64,67,71,75,79,83,87],{"title":25,"path":65,"stem":66},"\u002Fapps\u002Fpersistence\u002Foverview","01.apps\u002F05.persistence\u002F01.overview",{"title":68,"path":69,"stem":70},"Defining Entities","\u002Fapps\u002Fpersistence\u002Fdefining-entities","01.apps\u002F05.persistence\u002F02.defining-entities",{"title":72,"path":73,"stem":74},"Entity Decorators","\u002Fapps\u002Fpersistence\u002Fentity-decorators","01.apps\u002F05.persistence\u002F03.entity-decorators",{"title":76,"path":77,"stem":78},"CRUD Operations","\u002Fapps\u002Fpersistence\u002Fcrud-operations","01.apps\u002F05.persistence\u002F04.crud-operations",{"title":80,"path":81,"stem":82},"Named Queries","\u002Fapps\u002Fpersistence\u002Fnamed-queries","01.apps\u002F05.persistence\u002F05.named-queries",{"title":84,"path":85,"stem":86},"Multi-Tenancy","\u002Fapps\u002Fpersistence\u002Fmulti-tenancy","01.apps\u002F05.persistence\u002F06.multi-tenancy",{"title":88,"path":89,"stem":90},"Migrations","\u002Fapps\u002Fpersistence\u002Fmigrations","01.apps\u002F05.persistence\u002F07.migrations",{"title":92,"icon":93,"path":94,"stem":95,"children":96,"page":36},"Security","i-lucide-shield-check","\u002Fapps\u002Fsecurity","01.apps\u002F06.security",[97],{"title":98,"path":99,"stem":100,"icon":101},"Authentication","\u002Fapps\u002Fsecurity\u002Fauthentication","01.apps\u002F06.security\u002F01.authentication","i-lucide-key-round",{"title":103,"icon":104,"path":105,"stem":106,"children":107,"page":36},"Deployment","i-lucide-cloud-upload","\u002Fapps\u002Fdeployment","01.apps\u002F07.deployment",[108,113,118],{"title":109,"path":110,"stem":111,"icon":112},"Deployment Workflow","\u002Fapps\u002Fdeployment\u002Fworkflow","01.apps\u002F07.deployment\u002F01.workflow","i-lucide-git-branch",{"title":114,"path":115,"stem":116,"icon":117},"Environments","\u002Fapps\u002Fdeployment\u002Fenvironments","01.apps\u002F07.deployment\u002F02.environments","i-lucide-server",{"title":119,"path":120,"stem":121,"icon":6},"Push to Deploy","\u002Fapps\u002Fdeployment\u002Fpush-to-deploy","01.apps\u002F07.deployment\u002F03.push-to-deploy",{"title":123,"path":124,"stem":125,"icon":126},"CLI Reference","\u002Fapps\u002Fcli-reference","01.apps\u002F08.cli-reference","i-lucide-terminal",{"title":128,"icon":129,"path":130,"stem":131,"children":132,"page":36},"Reference","i-lucide-book-open","\u002Fapps\u002Freference","01.apps\u002F09.reference",[133,138],{"title":134,"path":135,"stem":136,"icon":137},"Decorators Reference","\u002Fapps\u002Freference\u002Fdecorators","01.apps\u002F09.reference\u002F01.decorators","i-lucide-at-sign",{"title":139,"path":140,"stem":141,"icon":60},"Migration SQL Grammar","\u002Fapps\u002Freference\u002Fmigration-sql-grammar","01.apps\u002F09.reference\u002F02.migration-sql-grammar",{"title":143,"icon":117,"path":144,"stem":145,"children":146,"page":36},"Kinotic OS","\u002Fplatform","02.platform",[147,152,156,161,166,171,175,180,185,190,195],{"title":148,"path":149,"stem":150,"icon":151},"System Architecture","\u002Fplatform\u002Farchitecture","02.platform\u002F01.architecture","i-lucide-boxes",{"title":153,"path":154,"stem":155,"icon":6},"Deployment Guide","\u002Fplatform\u002Fdeployment-guide","02.platform\u002F02.deployment-guide",{"title":157,"path":158,"stem":159,"icon":160},"Configuration","\u002Fplatform\u002Fconfiguration","02.platform\u002F03.configuration","i-lucide-settings",{"title":162,"path":163,"stem":164,"icon":165},"Organization Management","\u002Fplatform\u002Forganization-management","02.platform\u002F04.organization-management","i-lucide-building",{"title":167,"path":168,"stem":169,"icon":170},"System Security","\u002Fplatform\u002Fsystem-security","02.platform\u002F05.system-security","i-lucide-shield",{"title":172,"path":173,"stem":174,"icon":93},"Defense in Depth","\u002Fplatform\u002Fdefense-in-depth","02.platform\u002F06.defense-in-depth",{"title":176,"path":177,"stem":178,"icon":179},"MCP Tools","\u002Fplatform\u002Fmcp-tools","02.platform\u002F07.mcp-tools","i-lucide-bot",{"title":181,"path":182,"stem":183,"icon":184},"Observability","\u002Fplatform\u002Fobservability","02.platform\u002F08.observability","i-lucide-activity",{"title":186,"path":187,"stem":188,"icon":189},"Contributing","\u002Fplatform\u002Fcontributing","02.platform\u002F09.contributing","i-lucide-git-pull-request",{"title":191,"path":192,"stem":193,"icon":194},"System Migrations","\u002Fplatform\u002Fsystem-migrations","02.platform\u002F10.system-migrations","i-lucide-database-zap",{"title":128,"icon":129,"path":196,"stem":197,"children":198,"page":36},"\u002Fplatform\u002Freference","02.platform\u002F11.reference",[199,204,209],{"title":200,"path":201,"stem":202,"icon":203},"CRI Format","\u002Fplatform\u002Freference\u002Fcri-format","02.platform\u002F11.reference\u002F01.cri-format","i-lucide-link",{"title":205,"path":206,"stem":207,"icon":208},"Grind Jobs","\u002Fplatform\u002Freference\u002Fgrind-jobs","02.platform\u002F11.reference\u002F02.grind-jobs","i-lucide-workflow",{"title":210,"path":211,"stem":212,"icon":151},"Project Publishing Design","\u002Fplatform\u002Freference\u002Fproject-publishing-design","02.platform\u002F11.reference\u002F03.project-publishing-design",{"id":214,"title":210,"body":215,"description":1393,"extension":1394,"links":1395,"meta":1396,"navigation":1397,"path":211,"seo":1398,"stem":212,"__hash__":1399},"docs\u002F02.platform\u002F11.reference\u002F03.project-publishing-design.md",{"type":216,"value":217,"toc":1378},"minimark",[218,222,244,248,258,281,328,332,365,391,395,502,523,527,558,630,636,649,653,680,686,692,698,745,763,767,799,803,834,924,928,949,953,1000,1037,1041,1044,1047],[219,220,25],"h2",{"id":221},"overview",[223,224,225,226,230,231,234,235,240,241,243],"p",{},"This page is the design record for publishing a project's artifacts: every microservice\nunder ",[227,228,229],"code",{},"packages\u002Fmicroservices"," runs in a VM of its own, and every UI under ",[227,232,233],{},"packages\u002Fui","\nis built by the deployment, uploaded to organization-owned Azure Blob storage, and served\non its own hostname through Azure Front Door. It describes the target shape, and the\n",[236,237,239],"a",{"href":238},"#built-so-far","Built so far"," section at the end says which parts exist today. The\nuser-facing view of the same flow is ",[236,242,119],{"href":120},".",[219,245,247],{"id":246},"entities","Entities",[249,250,255],"pre",{"className":251,"code":253,"language":254},[252],"language-text","ProjectDeployment            one per project: checkout dir, sync and publish VMs, job run, status,\n  │                          the artifacts the last sync found and the commit it found them in\n  ├─ MicroserviceDeployment  one per microservice artifact: its VM, its machine identity, the\n  │                          commit it was ensured for, status\n  └─ UiDeployment            one per UI artifact: hostname label, Front Door resources, live\n                             commit, status\n","text",[227,256,253],{"__ignoreMap":257},"",[223,259,260,261,264,265,268,269,272,273,276,277,280],{},"\"Deployment\" names standing deployed state, as ",[227,262,263],{},"ProjectDeployment"," already does. Both\nchildren are plain ",[227,266,267],{},"AbstractRepository"," entities in management-api beside ",[227,270,271],{},"Workload",", not\norganization-scoped ones. Each child has a published service for the console with\n",[227,274,275],{},"findAllForProject"," and ",[227,278,279],{},"remove","; removal is the only path that destroys anything.",[223,282,283,284,287,288,291,292,295,296,299,300,303,304,299,307,299,310,299,313,316,317,319,320,323,324,327],{},"Module placement follows the two servers the platform is splitting into: a management server\nrunning management-api alone, which the portal and every organization machine reach, and a\nsystem server running management-api and system-api, which the system console reaches. So\nthe records, their repositories and every service the portal calls live in management-api;\neverything that touches nodes, workloads or Azure lives in system-api; and the management\nplane reaches the second only through ",[227,285,286],{},"DeploymentOperationsProxy",", a ",[227,289,290],{},"@Proxy"," interface onto\nthe system zone's ",[227,293,294],{},"DeploymentOperationsService"," (",[227,297,298],{},"restartMicroservice",", ",[227,301,302],{},"removeMicroservice",",\n",[227,305,306],{},"checkUiSite",[227,308,309],{},"provisionUiSite",[227,311,312],{},"removeUiSite",[227,314,315],{},"provisionOrganization","). The management\nservices authorize a request against the caller's organization, then delegate; the\noperations service trusts its callers.\n",[227,318,263],{}," keeps the sync identity and loses ",[227,321,322],{},"runtimeWorkloadId"," and\n",[227,325,326],{},"runtimeMachineIdentityId",", which move to the per-microservice rows.",[219,329,331],{"id":330},"artifacts-and-their-identity","Artifacts and their identity",[223,333,334,335,338,339,342,343,345,346,349,350,353,354,356,357,360,361,364],{},"The ",[236,336,337],{"href":34},"artifacts"," a commit contains are found\ninside the sync VM, over the checkout, by TypeScript in the workload-runner: code that sits\nin the Bun ecosystem and can later resolve packages the way the project's own tooling\n(",[227,340,341],{},"bunup",") does. For now the rule is naive and reads no Bun or bunup configuration: a\nmicroservice is a package directly under ",[227,344,229],{}," with entry\n",[227,347,348],{},"package.json.main",", else ",[227,351,352],{},"src\u002Fmain.ts","; a UI is a package directly under ",[227,355,233],{}," with\na ",[227,358,359],{},"build"," script that writes ",[227,362,363],{},"dist\u002Findex.html",". The UI location is the one requirement that\nstays hard-coded when the resolution improves.",[223,366,367,368,371,372,375,376,379,380,383,384,387,388,390],{},"An artifact's identity is the unscoped part of the ",[227,369,370],{},"name"," in its ",[227,373,374],{},"package.json","\n(",[227,377,378],{},"@acme\u002Fadmin"," is ",[227,381,382],{},"admin","), a single zone label; a name that is not one fails the deploy\nnaming the package. The directory name never matters. The sync VM reports what it found\nthrough ",[227,385,386],{},"ProjectArtifactService.recordArtifacts",", authenticated as the project's sync\nmachine identity, and the server records it on ",[227,389,263],{}," with the commit it was\nfound in; the server applies the same name rule to what it is told.",[219,392,394],{"id":393},"the-deploy-job","The deploy job",[396,397,398,414],"table",{},[399,400,401],"thead",{},[402,403,404,408,411],"tr",{},[405,406,407],"th",{},"#",[405,409,410],{},"Task",[405,412,413],{},"What it does",[415,416,417,431,446,464,482],"tbody",{},[402,418,419,423,426],{},[420,421,422],"td",{},"1",[420,424,425],{},"Resolve deployment target",[420,427,428],{},[227,429,430],{},"DeployTarget(nodeId, hostDir, syncWorkloadId, uiPublishWorkloadId)",[402,432,433,436,439],{},[420,434,435],{},"2",[420,437,438],{},"Sync project source",[420,440,441,442,445],{},"git fetch\u002Fcheckout · bun install · find artifacts · ",[227,443,444],{},"kinotic sync --publish"," · build UIs · report artifacts · write the sentinel (still last)",[402,447,448,451,454],{},[420,449,450],{},"3",[420,452,453],{},"Resolve artifacts",[420,455,456,457,460,461],{},"binds the ",[227,458,459],{},"ProjectArtifacts(microservices, uis)"," the sync VM reported for the commit into the run; fails when the record names another commit. ",[227,462,463],{},"Store.state(ARTIFACTS).wire()",[402,465,466,469,472],{},[420,467,468],{},"4",[420,470,471],{},"Ensure runtime workloads",[420,473,474,475,478,479],{},"one VM per microservice artifact with one machine identity each; running ones restart on the sentinel, ended ones are replaced, missing ones created, vanished ones marked ",[227,476,477],{},"ORPHANED",". ",[227,480,481],{},"Store.state(MICROSERVICE_DEPLOYMENTS).wire()",[402,483,484,487,490],{},[420,485,486],{},"5",[420,488,489],{},"Publish UIs",[420,491,492,493,496,497,478,499],{},"requires the organization's storage ",[227,494,495],{},"READY"," (provisioned with the organization, never here) → container SAS → publish VM uploads → finalize rows; vanished UIs marked ",[227,498,477],{},[227,500,501],{},"Store.state(UI_DEPLOYMENTS).wire()",[223,503,504,505,508,509,512,513,516,517,520,521,243],{},"Placement: every VM of a project shares the node holding its checkout. A first deployment's\nprobe wants ",[227,506,507],{},"syncMemoryMb + microservices × runtimeMemoryMb",", but the artifacts are only known\nafter the sync VM has run on the chosen node; how the probe accounts for the microservices is\ndecided with the runtime-workload phase. The runtime workload of\neach service sets ",[227,510,511],{},"KINOTIC_APP_ENTRY = \u003Cdir>\u002F\u003Centry>","; the supervisor is unchanged.\n",[227,514,515],{},"ProjectDeployIdentityService.issueRuntimeCredentials"," reads and writes the identity id on\nthe ",[227,518,519],{},"MicroserviceDeployment"," row; the sync identity stays on ",[227,522,263],{},[219,524,526],{"id":525},"storage","Storage",[223,528,529,530,533,534,537,538,541,542,545,546,549,550,553,554,557],{},"One storage account per organization, named ",[227,531,532],{},"\"kin\" + hex(sha256(organizationId)).substring(0, 21)",":\nStorageV2, LRS, hierarchical namespace on, TLS 1.2, ",[227,535,536],{},"allowBlobPublicAccess=false",", public\nnetwork access open (Front Door reads from addresses the storage firewall cannot name, and\nevery read is authorized by the bearer token of the profile's identity), a private endpoint in the platform\nVNet registered in ",[227,539,540],{},"privatelink.blob.core.windows.net"," unless\n",[227,543,544],{},"kinotic.systemApi.organizationStorage.disablePrivateEndpoint"," is set, as it is where the server\nruns outside the VNet, tagged ",[227,547,548],{},"org=\u003Cid>",". It holds one container, ",[227,551,552],{},"sites"," (container names\nare 3 to 63 characters, so not ",[227,555,556],{},"ui",").",[223,559,560,561,564,565,568,569,303,572,299,575,276,578,295,581,299,584,586,587,590,591,594,595,599,600,603,604,478,607,610,611,614,615,618,619,622,623,625,626,629],{},"Recorded on ",[227,562,563],{},"Organization.storage",", an ",[227,566,567],{},"OrganizationStorage",": ",[227,570,571],{},"azureSubscriptionId",[227,573,574],{},"azureAccountName",[227,576,577],{},"azureBlobEndpoint",[227,579,580],{},"status",[227,582,583],{},"PROVISIONING",[227,585,495],{}," or ",[227,588,589],{},"FAILED",",\nwith a message). Provisioning is a grind job, ",[227,592,593],{},"provision-organization-\u003Cid>",", with a ",[596,597,598],"strong",{},"Provision\nstorage"," task and a ",[596,601,602],{},"Prepare Front Door"," task, both idempotent, owned by the organization\nand recorded on it as ",[227,605,606],{},"provisioningJobRunId",[227,608,609],{},"OrganizationService.provision"," runs every\n",[227,612,613],{},"OrganizationProvisioner"," (a domain hook) on the organization: the signup flow calls it once\nthe organization's record is complete, and ",[227,616,617],{},"SystemOrganizationService.provisionOrganization","\nruns it again on request. The management module's provisioner asks the system server to\nstart the job through the proxy and returns; the tasks record their outcome on the\norganization and the run shows in the system console's job runs and on the organization's\noverview, with a ",[596,620,621],{},"Provision again"," action. A deployment only\nreads the outcome, and fails naming the state when the storage is not ",[227,624,495],{},"; nothing is\nprovisioned by a deployment. A mock provisioner under\n",[227,627,628],{},"kinotic.systemApi.organizationStorage.disableProvisioner=true"," points every organization\nat the configured Azurite connection string.",[249,631,634],{"className":632,"code":633,"language":254},[252],"kin\u003Chash>\u002Fsites\u002Fprod\u002Forders\u002Fui\u002Fadmin\u002Findex.html          Cache-Control: no-cache; uploaded last: the atomic switch\nkin\u003Chash>\u002Fsites\u002Fprod\u002Forders\u002Fui\u002Fadmin\u002Fversion.json        Cache-Control: no-cache; { \"commitSha\": \"\u003Csha>\" }\nkin\u003Chash>\u002Fsites\u002Fprod\u002Forders\u002Fui\u002Fadmin\u002Fassets\u002F…            Cache-Control: public, max-age=31536000, immutable; names carry a content hash\nkin\u003Chash>\u002Fsites\u002Fprod\u002Forders\u002Fui\u002Fadmin\u002F…                   Cache-Control: no-cache; the rest of dist, e.g. favicon.ico\n",[227,635,633],{"__ignoreMap":257},[223,637,638,641,642,478,645,648],{},[227,639,640],{},"dist"," is uploaded as it is, so a build needs no base path; every blob carries metadata\n",[227,643,644],{},"commit=\u003Csha>",[227,646,647],{},"prod"," is one constant in the prefix builder; nothing else knows the\nenvironment. The finalize step deletes every blob under the UI's prefix stamped with a\ncommit other than the current one.",[219,650,652],{"id":651},"serving","Serving",[223,654,655,656,659,660,663,664,667,668,671,672,675,676,679],{},"One Front Door Standard profile and endpoint, created by terraform, with a system-assigned\nmanaged identity that terraform grants ",[596,657,658],{},"Storage Blob Data Reader"," on the resource group\nevery organization's storage account is created in. Per organization, created with the\norganization once its storage is ready and named by id by every site's route: origin group\n",[227,661,662],{},"org-\u003CorgId>",", authenticating to the origin as that identity (",[227,665,666],{},"SystemAssignedIdentity",",\nscope ",[227,669,670],{},"https:\u002F\u002Fstorage.azure.com\u002F.default","), with HTTPS health probes on the container's\nproperties, which origin authentication requires, and origin ",[227,673,674],{},"\u003Caccount>.blob.core.windows.net","\nwith the same origin host header. Front Door puts the identity's bearer token on every\nrequest it forwards, so no SAS travels in the configuration and a request's own query string\nreaches the origin unchanged. Origin authentication exists from API version ",[227,677,678],{},"2025-06-01",",\nwhich the Java CDN SDK does not speak yet, so the provisioner writes the origin group as JSON\nthrough the SDK's pipeline and polls the SDK's read until it is provisioned.",[223,681,682,683,685],{},"Shared by every route, created once, the rule set ",[227,684,552],{}," with one rule:",[249,687,690],{"className":688,"code":689,"language":254},[252],"spa    url_file_extension GreaterThan 0, negated  → rewrite \u002F  →  \u002Findex.html     preserve_unmatched_path = false\n",[227,691,689],{"__ignoreMap":257},[223,693,694,697],{},[227,695,696],{},"url_file_extension Any"," matches a path with no extension as well, so it cannot tell a file\nfrom a route of the single-page application; the extension's length can. A request naming a\nfile reaches the origin as it is, under the route's origin path.",[223,699,700,701,704,705,708,709,323,712,715,716,719,720,723,724,726,727,730,731,734,735,737,738,740,741,744],{},"Per site, created on first publish: a custom domain ",[227,702,703],{},"\u003Clabel>.\u003CsitesDomain>"," with a managed\ncertificate; DNS in the ",[227,706,707],{},"kinotic.ai"," zone, ",[227,710,711],{},"CNAME \u003Clabel>.apps → \u003Cprofile endpoint host>",[227,713,714],{},"TXT _dnsauth.\u003Clabel>.apps → \u003Cvalidation token>","; and a route for that domain with pattern\n",[227,717,718],{},"\u002F*",", HTTPS only with redirect (origin authentication requires HTTPS to the origin), the\norganization's origin group, origin path ",[227,721,722],{},"\u002Fsites\u002Fprod\u002F\u003Capp>\u002Fui\u002F\u003Cui>",", the shared rule set,\ncaching on and query strings ignored. A route naming another rule set is written again.\nFront Door writes are slow, serialized per profile, and answer 409 when one is in flight, so\nthe provisioner issues one write at a time per profile with backoff; a change takes up to\n15 minutes to reach every edge, longer when changes queue. A site is ",[227,725,583],{}," until\n",[227,728,729],{},"https:\u002F\u002F\u003Chostname>\u002Fversion.json"," answers through Front Door with the deployment's commit\nand ",[227,732,733],{},"https:\u002F\u002F\u003Chostname>\u002F"," answers with HTML (the root unrewritten is the UI's directory, an\nempty 200, and a file bypasses the spa rule), then ",[227,736,495],{},"; the domain's validation and\ncertificate flags say nothing about the route, the rule set or the propagation, so they are\nnot consulted, and an earlier configuration of the same site may still answer while a new\none propagates. ",[227,739,589],{}," with the message when the\ndomain's validation cannot succeed, with ",[227,742,743],{},"retryProvisioning",". The provisioner checks a\nprovisioning site every 30 seconds for up to two hours after provisioning it and records the\noutcome on its row; a site still provisioning after that, or one whose polling died with the\nserver, is checked again whenever its project's UI deployments are listed.",[223,746,747,748,751,752,295,755,758,759,762],{},"The hostname label is ",[227,749,750],{},"{org}-{app}-{ui}"," under ",[227,753,754],{},"sitesDomain",[227,756,757],{},"apps.kinotic.ai","), minted once\nat first publish, stored as ",[227,760,761],{},"UiDeployment.id",", looked up by hostname and never parsed. The\nrepository enforces uniqueness with a numeric suffix on collision. A label is at most 63\ncharacters, else the publish fails naming the organization and application. There is no\nwildcard DNS record: each site has its own CNAME.",[219,764,766],{"id":765},"auth","Auth",[223,768,769,772,773,776,777,780,781,784,785,788,789,541,792,795,796,798],{},[227,770,771],{},"*.apps.kinotic.ai"," is same-site with ",[227,774,775],{},"api.kinotic.ai",", so the session cookie works unchanged\nand CORS already admits ",[227,778,779],{},"(.+\\.)?kinotic\\.ai",". The session cookie is named\n",[227,782,783],{},"__Host-kinotic-session"," (Secure and ",[227,786,787],{},"Path=\u002F"," are already set), ",[227,790,791],{},"SameSite=Lax",[227,793,794],{},"kinotic.apiGateway.sessionCookieSameSite"," says otherwise, as a developer's profile does when\nthe sites and the API sit on unrelated domains. ",[227,797,757],{}," is never\nput on the Public Suffix List.",[219,800,802],{"id":801},"build-and-upload-contracts","Build and upload contracts",[223,804,805,806,299,809,323,812,815,816,295,819,822,823,826,827,829,830,833],{},"The sync step sets, per UI build, ",[227,807,808],{},"VITE_KINOTIC_HOST",[227,810,811],{},"VITE_KINOTIC_PORT",[227,813,814],{},"VITE_KINOTIC_USE_SSL",", split from ",[227,817,818],{},"KINOTIC_UI_SERVER_URL",[227,820,821],{},"DomainProperties.resolveApiBaseUrl()",",\nplaced on the sync workload by the job factory; ",[227,824,825],{},"DeploymentProperties.serverHost"," is an IPv4\nfor egress and not usable by a browser). They are the variables the platform's own consoles\nconnect with, and Vite exposes them to the page without configuration. A build that leaves no ",[227,828,363],{}," fails the deploy before the\nsentinel. ",[227,831,832],{},"artifacts.ts"," in workload-runner is the one enumeration of the artifacts, shared\nby the sync and publish entrypoints.",[223,835,836,837,840,841,844,845,848,849,852,853,856,857,860,861,864,865,868,869,872,873,875,876,879,880,883,884,751,886,889,890,893,894,897,898,901,902,905,906,299,909,303,912,915,916,919,920,923],{},"The publish workload is named ",[227,838,839],{},"project-ui-publish-\u003CprojectId>",", with id\n",[227,842,843],{},"DeployTarget.uiPublishWorkloadId"," decided in ",[227,846,847],{},"resolveTarget"," like ",[227,850,851],{},"syncWorkloadId",". It runs\nthe same image in the foreground with entrypoint ",[227,854,855],{},"bun src\u002Fpublish-ui.ts",", the checkout mounted\nread-only at ",[227,858,859],{},"\u002Fworkspace",", env ",[227,862,863],{},"KINOTIC_UI_COMMIT",", and the secret ",[227,866,867],{},"KINOTIC_UI_UPLOAD_URL"," =\n",[227,870,871],{},"\u003Cblob endpoint>\u002Fsites\u002Fprod\u002F\u003Capp>\u002Fui?\u003Ccontainer SAS, create+write, TTL the run>",". Its allowed\nhosts are the host that upload URL names only — the organization's account, which the\nplatform network resolves to its private endpoint, or the Azurite standing in for it; it carries no Kinotic\ncredentials and no machine identity, is kept after its run, and is retired by the next run's\n",[227,874,847],{},". Its exit check is the one ",[227,877,878],{},"syncSource"," uses, extracted to one method with two\nconsumers. ",[227,881,882],{},"publish-ui.ts"," uploads, per UI, everything in ",[227,885,640],{},[227,887,888],{},"\u003Cname>\u002F",": the files under\n",[227,891,892],{},"assets\u002F"," with the immutable header, the rest uncached, then ",[227,895,896],{},"version.json",", then\n",[227,899,900],{},"index.html",", each a ",[227,903,904],{},"PUT"," with ",[227,907,908],{},"x-ms-blob-type: BlockBlob",[227,910,911],{},"x-ms-blob-cache-control",[227,913,914],{},"x-ms-meta-commit",", and a ",[227,917,918],{},"Content-Type"," from ",[227,921,922],{},"Bun.file().type","; small concurrency, one retry\non 5xx, non-zero exit on failure.",[219,925,927],{"id":926},"orphans-and-removal","Orphans and removal",[223,929,930,931,933,934,937,938,940,941,944,945,948],{},"An artifact missing from a deploy marks its deployment ",[227,932,477],{},"; it keeps running or serving\nand nothing is deleted. An artifact that returns is adopted (status back to ",[227,935,936],{},"DEPLOYED"," or\n",[227,939,495],{},"), never re-provisioned. ",[227,942,943],{},"MicroserviceDeploymentService.remove"," destroys the VM,\nremoves the identity, and deletes the row. ",[227,946,947],{},"UiDeploymentService.remove"," deletes the route, the\ndomain, both DNS records, the blob prefix, and the row. Both confirm in the console when the\ndeployment is not orphaned.",[219,950,952],{"id":951},"properties-and-dependencies","Properties and dependencies",[223,954,955,568,958,299,961,299,964,303,967,299,970,299,973,299,976,303,979,982,983,568,986,299,988,299,991,303,994,299,997,999],{},[227,956,957],{},"kinotic.systemApi.organizationStorage.*",[227,959,960],{},"subscriptionIds",[227,962,963],{},"resourceGroup",[227,965,966],{},"location",[227,968,969],{},"privateEndpointSubnetId",[227,971,972],{},"privateDnsZoneId",[227,974,975],{},"azuriteConnectionString",[227,977,978],{},"disableProvisioner",[227,980,981],{},"disablePrivateEndpoint",".\n",[227,984,985],{},"kinotic.systemApi.uiDeployment.*",[227,987,754],{},[227,989,990],{},"dnsZoneId",[227,992,993],{},"frontDoorProfileId",[227,995,996],{},"frontDoorEndpointHostName",[227,998,978],{},". Nothing else is a property.",[223,1001,1002,1003,1006,1007,1010,1011,1014,1015,303,1018,299,1021,303,1024,299,1027,303,1030,478,1033,1036],{},"Managed artifacts, each a ",[227,1004,1005],{},"*Version"," in ",[227,1008,1009],{},"gradle.properties"," and one line in the conventions\n",[227,1012,1013],{},"dependencyManagement"," block: ",[227,1016,1017],{},"com.azure:azure-storage-blob",[227,1019,1020],{},"com.azure:azure-resourcemanager-storage",[227,1022,1023],{},"com.azure:azure-resourcemanager-network",[227,1025,1026],{},"com.azure:azure-resourcemanager-privatedns",[227,1028,1029],{},"com.azure:azure-resourcemanager-dns",[227,1031,1032],{},"com.azure:azure-resourcemanager-cdn",[227,1034,1035],{},"azure-identity"," is already managed.",[219,1038,1040],{"id":1039},"deferred","Deferred",[223,1042,1043],{},"Not part of this design: customer domains, an Environment entity, customer file storage, push\nnotification of publishes, and service endpoints instead of private endpoints.",[219,1045,239],{"id":1046},"built-so-far",[1048,1049,1050,1092,1118,1137,1202,1231,1264,1287,1329,1358],"ul",{},[1051,1052,1053,1056,1057,1060,1061,1063,1064,323,1066,1069,1070,1072,1073,1076,1077,1080,1081,1084,1085,1088,1089,1091],"li",{},[596,1054,1055],{},"Artifact discovery."," The sync VM finds the commit's artifacts (",[227,1058,1059],{},"src\u002Fartifacts.ts"," in the\nworkload-runner, the naive rule above) and reports them through\n",[227,1062,386],{},", which records ",[227,1065,337],{},[227,1067,1068],{},"artifactsCommitSha"," on ",[227,1071,263],{},". The deploy job's third task, ",[596,1074,1075],{},"Resolve\nartifacts",", binds them into the run as a ",[227,1078,1079],{},"ProjectArtifacts"," (its ",[227,1082,1083],{},"MicroserviceArtifact","s\nand ",[227,1086,1087],{},"UiArtifact","s) under the ",[227,1090,337],{}," store name, wired to watchers. The job run page\nof the portal and of the system console lists what the commit contains on that task's row,\nand the sync task's row expands into the build VM's log in both.",[1051,1093,1094,1097,1098,1100,1101,1104,1105,1107,1108,478,1111,1113,1114,1117],{},[596,1095,1096],{},"One VM per microservice."," ",[227,1099,519],{}," rows, keyed ",[227,1102,1103],{},"\u003CprojectId>:\u003Cname>"," so\nthe store enforces one per microservice, record each microservice's workload, machine\nidentity, entry point and commit. The deploy job's fourth task, ",[596,1106,471],{},",\nkeeps running VMs, replaces ended ones and ones whose entry point moved, creates missing ones,\norphans vanished ones, records failures per row, and stores the rows under\n",[227,1109,1110],{},"microserviceDeployments",[227,1112,263],{}," no longer carries a runtime workload or\nidentity; ",[227,1115,1116],{},"findProjectMachines"," lists the sync identity then one per microservice.",[1051,1119,1120,1097,1123,295,1126,303,1128,299,1131,1133,1134,1136],{},[596,1121,1122],{},"Microservices in the console.",[227,1124,1125],{},"MicroserviceDeploymentService",[227,1127,275],{},[227,1129,1130],{},"restart",[227,1132,279],{},"), published from management-api and reaching the VM through\n",[227,1135,286],{},", and the portal's deployment page: a microservices table with logs, restart and remove, and the\nmachines labelled by the deployment that records them.",[1051,1138,1139,1097,1142,1145,1146,1148,1149,1151,1152,1155,1156,299,1158,586,1160,1162,1163,1165,1166,1168,1169,1172,1173,1175,1176,1178,1179,1182,1183,1186,1187,1190,1191,1194,1195,1198,1199,1201],{},[596,1140,1141],{},"Organization storage.",[227,1143,1144],{},"AzureOrganizationStorageProvisioner"," creates the account, the\n",[227,1147,552],{}," container, and, unless ",[227,1150,981],{}," is set, the private endpoint with its\nDNS zone group, recording the outcome on\n",[227,1153,1154],{},"Organization"," with a status of ",[227,1157,583],{},[227,1159,495],{},[227,1161,589],{},". It is the first task\nof the ",[227,1164,593],{}," job that ",[227,1167,294],{}," runs on the\nsystem server when the organization is created, asked through the proxy by\n",[227,1170,1171],{},"DefaultOrganizationProvisioner",", the management module's ",[227,1174,613],{},", and\nwhenever the system console's ",[596,1177,621],{}," asks; the Front Door preparation is the\nsecond. The provisioners, the storage service and their settings live in system-api. A\ndeployment that publishes a UI reads the outcome and fails when the storage is not ready.\n",[227,1180,1181],{},"AzureProvisioningIntegrationTest"," in system-api runs both provisioners against a\ndeveloper's subscription, from the ",[227,1184,1185],{},"local"," profile and ",[227,1188,1189],{},".env.local",", and skips elsewhere.\n",[227,1192,1193],{},"MockOrganizationStorageProvisioner"," points every organization at Azurite. Terraform owns the resource group, private-endpoints subnet, private DNS zone and the\nkinotic-server roles. The account's public network stays open, with anonymous access off,\nso Front Door can read it; the platform comes in through the private endpoint, or over the\npublic endpoint where it has none. A developer runs the real path against their own\nsubscription with the ",[227,1196,1197],{},"deployment\u002Fterraform\u002Fazure\u002Fdev"," root and the ",[227,1200,1185],{}," profile.",[1051,1203,1204,1207,1208,1211,1212,1215,1216,299,1218,276,1220,1222,1223,1225,1226,1228,1229,243],{},[596,1205,1206],{},"UIs built in the sync VM."," After the entity sync, ",[227,1209,1210],{},"sync.ts"," runs ",[227,1213,1214],{},"bun run build"," in\nevery UI artifact with ",[227,1217,808],{},[227,1219,811],{},[227,1221,814],{},",\nsplit from the ",[227,1224,818],{}," placed on the sync workload from\n",[227,1227,821],{},", and fails the run naming a UI whose build leaves no\n",[227,1230,363],{},[1051,1232,1233,1097,1236,1239,1240,295,1243,299,1246,299,1249,303,1252,1255,1256,1259,1260,1263],{},[596,1234,1235],{},"UI deployments and the storage data plane.",[227,1237,1238],{},"UiDeployment"," rows keyed by the site's\nhostname label, ",[227,1241,1242],{},"OrganizationStorageService",[227,1244,1245],{},"issueUploadUrl",[227,1247,1248],{},"exists",[227,1250,1251],{},"listCommitDirs",[227,1253,1254],{},"deletePrefix",") over the blob SDK or Azurite, ",[227,1257,1258],{},"UiStoragePaths"," as the one home of the\ncontainer layout, and the ",[227,1261,1262],{},"UiDeploymentProvisioner"," contract with a mock that marks sites\nready at once.",[1051,1265,1266,1269,1270,1272,1273,751,1275,1277,1278,1280,1281,1283,1284,1286],{},[596,1267,1268],{},"The publish task."," The deploy job's fifth task, ",[596,1271,489],{},", runs\n",[227,1274,839],{},[227,1276,843],{}," with\n",[227,1279,882],{},", the checkout read-only, ",[227,1282,863],{},", the secret\n",[227,1285,867],{}," (a one-hour container SAS) and the storage account's hostname as its\nonly egress, then finalizes: mints labels with numeric suffixes on collision, provisions new\nsites, adopts returning ones, orphans vanished ones, keeps the files of the current\ncommit and deletes the rest. The exit check is shared with the sync task, and the\nprevious publish workload is retired by the next run's target resolution.",[1051,1288,1289,1097,1292,1295,1296,1298,1299,1301,1302,1305,1306,1309,1310,1312,1313,1315,1316,982,1318,1320,1321,1324,1325,1328],{},[596,1290,1291],{},"Sites on Front Door.",[227,1293,1294],{},"FrontDoorUiDeploymentProvisioner"," creates, per organization\nwhen it is provisioned, the origin group ",[227,1297,662],{}," on the storage account's blob host,\nauthenticating as the profile's managed identity, and once the shared rule set ",[227,1300,552],{},"\nwith the ",[227,1303,1304],{},"spa"," rewrite rule; per site the custom domain with a managed certificate, the\nCNAME and ",[227,1307,1308],{},"_dnsauth"," TXT records in the platform zone, and the route with the UI's prefix\nas origin path, naming the organization's origin group and the shared rule set by id.\nEvery step is get-or-create, a lapsed validation gets a new token, profile writes are\nqueued and retried on 409, and a provisioning site is polled in the background until\n",[227,1311,896],{}," serves its commit through Front Door and it is ",[227,1314,495],{},", or it is ",[227,1317,589],{},[227,1319,279],{}," deletes the route, the domain and the two records. ",[227,1322,1323],{},"AzureUtil"," classifies the\nmanagement plane's 404 and 409 for both provisioners. Terraform (",[227,1326,1327],{},"frontdoor.tf",") owns the\nprofile, its identity and that identity's Storage Blob Data Reader on the organization\nstorage group, and grants the server CDN Profile Contributor and DNS Zone Contributor; the\nstorage account's public network is open so Front Door can read it.",[1051,1330,1331,1097,1334,295,1337,303,1339,299,1341,1343,1344,1346,1347,1350,1351,1354,1355,1357],{},[596,1332,1333],{},"UI deployments in the console.",[227,1335,1336],{},"UiDeploymentService",[227,1338,275],{},[227,1340,743],{},[227,1342,279],{},"), published from management-api and delegating to the\nsystem server through ",[227,1345,286],{},": listing has any site left provisioning\nfor ten minutes or more checked, so one whose polling died with the server still advances;\nretry runs the provisioner again and records the outcome; remove takes the site down,\ndeletes the UI's prefix in storage and deletes the row. Deleting a project removes\nits UI deployments the same way. ",[227,1348,1349],{},"@kinotic-ai\u002Fmanagement-api"," exposes it as\n",[227,1352,1353],{},"Kinotic.uiDeployments"," with the ",[227,1356,1238],{}," model.",[1051,1359,1360,1097,1363,1366,1367,1369,1370,1373,1374,1377],{},[596,1361,1362],{},"Sites in the console.",[227,1364,1365],{},"UiDeployment.url"," is minted with the\nlabel, so the portal's deployment page lists each UI with a link to its site, status and\ncommit, with retry and removal. The session cookie is ",[227,1368,783],{},", its name\nshared through ",[227,1371,1372],{},"EventConstants.SESSION_COOKIE_NAME"," by the gateway's session handler and the\n",[227,1375,1376],{},"\u002Fapi\u002Fauth\u002Fme"," route.",{"title":257,"searchDepth":1379,"depth":1379,"links":1380},2,[1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392],{"id":221,"depth":1379,"text":25},{"id":246,"depth":1379,"text":247},{"id":330,"depth":1379,"text":331},{"id":393,"depth":1379,"text":394},{"id":525,"depth":1379,"text":526},{"id":651,"depth":1379,"text":652},{"id":765,"depth":1379,"text":766},{"id":801,"depth":1379,"text":802},{"id":926,"depth":1379,"text":927},{"id":951,"depth":1379,"text":952},{"id":1039,"depth":1379,"text":1040},{"id":1046,"depth":1379,"text":239},"How a project's microservices and UIs are deployed, stored, and served, and how far the build has come.","md",null,{},{"icon":151},{"title":210,"description":1393},"43_AieJcccCxLFH1mJ48zbtwhLgHGXrBa1xCD8dcV_Q",[1401,1395],{"title":205,"path":206,"stem":207,"description":1402,"icon":208,"children":-1},"The declarative job framework for long-running platform work.",1788549867581]