System Admin App Version Management
This runbook covers the Native minimum-version gate and Capacitor Bundle publication. The public contract is in the App startup protocol.
Data and access boundary
System Admin stores one standard publication per appId. Native minimum build numbers and the selected Bundle are published together as an immutable revision. All organizations use that revision; endpoint pages do not contain Bundle assignments.
S3 is the candidate source. System Admin does not mirror the S3 directory, upload or delete objects, call GitHub, or publish the newest upload automatically. Refreshing the list is read-only. A publication snapshot stores verified S3 object metadata and a stable download URL, so later source changes cannot rewrite an existing response or history record.
The exact public paths are:
/api/public/v2/app-versions/check/api/public/v2/app-bundles/resolve/api/public/v1/backend-discovery/resolve
These paths use the existing public-origin, body-size, rate-limit, and Cache-Control: no-store protections. Administrator routes and the static console remain private.
Deployment environment and transport policy
The server uses the process NODE_ENV as the source of truth for URL transport policy. production requires HTTPS; development, test, and staging allow HTTP or HTTPS. No additional System Admin environment variable is required.
NODE_ENV | App backend URL | Native update URL | Bundle download URL |
|---|---|---|---|
production | HTTPS only | HTTPS only | HTTPS only |
development / test / staging | HTTP or HTTPS | HTTP or HTTPS | HTTP or HTTPS |
Allowing HTTP in development, test, or staging does not allow private IPs, localhost, credentials, ports, query strings, fragments, redirects, or arbitrary hosts. The same public-DNS and destination checks remain in place. production rejects HTTP before the download request is sent.
Distribution source configuration
Configure one source per app from 앱 버전:
| Field | Meaning |
|---|---|
| AWS Region | S3 region used by the server IAM role |
| S3 Bucket | Bucket containing the release prefix |
| Version prefix | Non-empty S3 key root, for example employee-app/releases/ |
| CloudFront download root | Public HTTP(S) root corresponding to that S3 prefix |
The server normalizes a trailing / on the prefix and download root. AWS keys, role ARNs, arbitrary endpoints, query strings, and signed URLs are never entered in the UI. Saving and validating require app_version:manage.global and the existing recent-reauthentication gate.
Validation checks configuration syntax, S3 list/head access, at least one valid Bundle object, and download-root reachability. CloudFront checks reject private destinations, redirects, timeouts, oversized responses, and size mismatches. A successful S3 list alone is not a successful download validation.
Bundle layout and immutable objects
CI uploads one shared ZIP for iOS and Android under a version directory:
<prefix>/1.2.3/bundle.zip
<prefix>/1.2.4/bundle.zipThe directory name is the Bundle version. manifest.json is not required and is ignored if present. Objects outside the exact <version>/bundle.zip shape, invalid version names, empty files, and files whose metadata or download check fails are unavailable for publication.
The server reads a trusted S3 SHA-256 checksum when available and otherwise computes SHA-256 by streaming the complete object. It never treats an ETag or multipart composite checksum as the file digest. Candidate identity includes the source revision, object key/version, and checksum; publication re-reads the source and rejects a changed object.
The ZIP is recorded as artifactKey: "bundle" and supports both ios and android. A publication may select the same candidate for both platforms. Existing snapshot rows retain nullable legacy manifest columns for audit compatibility; new candidates and public responses have no manifest dependency.
Administrator workflow
- Open 앱 버전, choose the app, and enter the reviewed Region, Bucket, version prefix, and download root.
- Save the source and run 연결 확인. Fix IAM, Bundle, or download failures before publishing.
- Refresh the S3 list. Candidates are sorted by S3
LastModified; the newest timestamp is marked(최신). The published record is independently marked(현재 사용). - Select the shared Bundle for iOS and/or Android, enter each selected platform's Native minimum build number, enter a reason, and save the draft.
- Preview and publish the standard policy. The transaction re-reads S3, checks the source revision and candidate identity, creates or reuses immutable snapshots, updates the publication, writes history, and records the admin audit.
- For a fix, upload a new immutable version and repeat the selection/publish flow. There is no withdraw, restore, or history rollback button in this model.
Read-only administrators can inspect the source, real-time candidates, current snapshot, and history but cannot save, validate, or publish. A source read failure never deletes the current snapshot from the page or changes the public response.
Public startup APIs
Native gate
POST /api/public/v2/app-versions/check
{
"appId": "hereby-works",
"platform": "ios",
"nativeReleaseNumber": 110
}The response is update_required when the build is below the published platform minimum and allowed otherwise. The App owns the Store URL and mandatory-update screen. A missing policy is 404 NATIVE_POLICY_NOT_FOUND.
Bundle resolution
POST /api/public/v2/app-bundles/resolve
{
"appId": "hereby-works",
"platform": "ios",
"nativeReleaseNumber": 110,
"currentBundleVersion": "1.2.2"
}The service checks the Native minimum again from the same publication. It returns native_update_required without a Bundle when the build is too old, up_to_date when the running Bundle version equals the published target, and update_available otherwise. The response includes the Bundle version, download URL, SHA-256 checksum, size, and release notes; it never exposes S3 or IAM configuration.
App and Infra responsibilities
- App: keep the prod and dev System Admin entry points, let developer mode select the dev entry, confirm any pending Bundle before remote checks, call
notifyAppReadyon every native launch, read the Native build and running Bundle version, call the startup APIs, download and verify the ZIP, apply it with Capacitor Updater, restart, confirm readiness, avoid update loops, and handle retry/fallback. - CI/Infra: upload an immutable
bundle.zipat the agreed version path, provide the reviewed IAM role and download-root mapping, and configure environment-specific System Admin URLs. - Sysadmin: validate the source, list completed candidates, publish the selected standard, and return only the published snapshot through the public APIs.
The App team must also verify iOS/Android cleartext-network policy. Saving an HTTP URL in Dev or Staging does not guarantee that the App can access it.
Migration and safety
Run RemoveAppVersionChannels1894000000009 after backing up the independent control-plane database. The migration fails closed if removing the obsolete channel dimension would create duplicate release, policy, source, snapshot, or publication keys. It preserves publication history and does not reset users, credentials, MFA factors, roles, sessions, or hospital discovery data. The migration is forward-only; use a separately reviewed forward migration if restoration is ever required.