Compare commits
20 Commits
4bcf88b9ee
...
53629d963d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53629d963d | ||
|
|
9933185ad9 | ||
|
|
2e263c1fdc | ||
|
|
2c57b04ed6 | ||
|
|
d3c35a46b4 | ||
|
|
edd76316eb | ||
|
|
7e59b82f6f | ||
|
|
74bd2c9b85 | ||
|
|
f1d0506cb1 | ||
|
|
c3be89e1dd | ||
|
|
a5422cbb27 | ||
|
|
712cf9ff00 | ||
|
|
1ac5e62ad4 | ||
|
|
2a9ba8da93 | ||
|
|
62125f7a90 | ||
|
|
d69a5017af | ||
|
|
025d9b18f8 | ||
|
|
b60db8c154 | ||
|
|
88e3aaae29 | ||
|
|
79cbb956a0 |
@@ -14,9 +14,26 @@ If you change architecture, deployment flow, or operational behavior, update the
|
|||||||
- `develop`: active integration branch
|
- `develop`: active integration branch
|
||||||
- `main`: stable branch for production promotion
|
- `main`: stable branch for production promotion
|
||||||
- short-lived feature branches: branch from `develop`, merge back into `develop`
|
- short-lived feature branches: branch from `develop`, merge back into `develop`
|
||||||
|
- customer release branches: branch from `main` for a frozen customer line when a customer must not receive future feature work automatically
|
||||||
|
|
||||||
Do not use `main` as the default development branch.
|
Do not use `main` as the default development branch.
|
||||||
|
|
||||||
|
### Customer release policy
|
||||||
|
For customer-specific deliveries such as TUBCO, use a dedicated release branch from the current stable line.
|
||||||
|
|
||||||
|
Recommended pattern:
|
||||||
|
- `release/tubco-v1`: customer maintenance branch
|
||||||
|
- `tubco-baseline-2026-03`: baseline tag taken from the same stable point
|
||||||
|
|
||||||
|
Rule:
|
||||||
|
- new product features continue on `develop`
|
||||||
|
- customer release branches only receive approved:
|
||||||
|
- bug fixes
|
||||||
|
- security updates
|
||||||
|
- UI improvements
|
||||||
|
|
||||||
|
Do not let a customer deployment track `develop` directly.
|
||||||
|
|
||||||
## Current Delivery Model
|
## Current Delivery Model
|
||||||
- GitHub Actions is used for CI
|
- GitHub Actions is used for CI
|
||||||
- the current test server is local/LAN-only
|
- the current test server is local/LAN-only
|
||||||
|
|||||||
@@ -7,6 +7,24 @@
|
|||||||
- GitHub Actions uploads the repository contents to the server over SSH
|
- GitHub Actions uploads the repository contents to the server over SSH
|
||||||
- the server does not need GitHub access to deploy
|
- the server does not need GitHub access to deploy
|
||||||
|
|
||||||
|
## Standard target model for later
|
||||||
|
When this moves from an internal test setup to a more standard delivery model, the target should be:
|
||||||
|
1. feature branch
|
||||||
|
2. CI
|
||||||
|
3. merge into `develop`
|
||||||
|
4. staging deploy
|
||||||
|
5. staging validation
|
||||||
|
6. merge into `main`
|
||||||
|
7. production deploy
|
||||||
|
|
||||||
|
Recommended standards for that future state:
|
||||||
|
- staging and production use separate hosts or clearly separated environments
|
||||||
|
- staging and production use separate secrets
|
||||||
|
- production deploys only from `main`
|
||||||
|
- production runs with HTTPS and `DJANGO_DEBUG=0`
|
||||||
|
- production deploys require environment protection or approval in GitHub
|
||||||
|
- rollback is documented and tested
|
||||||
|
|
||||||
This is intentional. For a private repository, server-side `git clone` adds unnecessary credential management.
|
This is intentional. For a private repository, server-side `git clone` adds unnecessary credential management.
|
||||||
|
|
||||||
## Branch strategy
|
## Branch strategy
|
||||||
@@ -491,6 +509,21 @@ docker compose --env-file .env.test -f docker-compose.prod.yml logs --tail=100 w
|
|||||||
docker compose --env-file .env.test -f docker-compose.prod.yml logs --tail=100 caddy
|
docker compose --env-file .env.test -f docker-compose.prod.yml logs --tail=100 caddy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## If localhost still looks wrong after the server is fixed
|
||||||
|
Use this order before assuming the local checkout is missing code:
|
||||||
|
1. hard refresh the page with `Cmd + Shift + R`
|
||||||
|
2. clear site data for `127.0.0.1:8088` in browser devtools and sign in again
|
||||||
|
3. restart the local web container:
|
||||||
|
```bash
|
||||||
|
docker compose restart web
|
||||||
|
```
|
||||||
|
4. if it still survives, rebuild the local stack:
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the correct recovery path for stale browser state, shared-header fixes, page-local CSS fixes, and versioned static asset updates.
|
||||||
|
|
||||||
## Rollback
|
## Rollback
|
||||||
This deployment path is source-upload based, not image-tag based.
|
This deployment path is source-upload based, not image-tag based.
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: min(var(--app-shell-width), 100%);
|
width: min(var(--app-shell-width), 100%);
|
||||||
margin: 0 auto 12px;
|
margin: 0 auto 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 30;
|
||||||
|
overflow: visible;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -137,6 +140,9 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 30;
|
||||||
|
overflow: visible;
|
||||||
padding: 22px 24px 18px;
|
padding: 22px 24px 18px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid rgba(217, 227, 238, 0.9);
|
border-bottom: 1px solid rgba(217, 227, 238, 0.9);
|
||||||
@@ -174,6 +180,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 31;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -730,14 +730,14 @@
|
|||||||
.inline-delete .btn { min-height: 38px; display: inline-flex; align-items: center; justify-content: center; }
|
.inline-delete .btn { min-height: 38px; display: inline-flex; align-items: center; justify-content: center; }
|
||||||
.intro-panel { min-width: 260px; }
|
.intro-panel { min-width: 260px; }
|
||||||
|
|
||||||
details {
|
td.intro-panel details {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: linear-gradient(180deg, #ffffff, #f8fbff);
|
background: linear-gradient(180deg, #ffffff, #f8fbff);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
details[open] {
|
td.intro-panel details[open] {
|
||||||
border-color: #cad7e8;
|
border-color: #cad7e8;
|
||||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.95);
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.95);
|
||||||
}
|
}
|
||||||
@@ -772,7 +772,7 @@
|
|||||||
color: var(--brand-blue);
|
color: var(--brand-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
details[open] .intro-toggle::after { content: "−"; }
|
td.intro-panel details[open] .intro-toggle::after { content: "−"; }
|
||||||
.intro-toggle::-webkit-details-marker { display: none; }
|
.intro-toggle::-webkit-details-marker { display: none; }
|
||||||
|
|
||||||
.intro-menu {
|
.intro-menu {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
<li><code>develop</code> is the active integration branch.</li>
|
<li><code>develop</code> is the active integration branch.</li>
|
||||||
<li><code>main</code> is the stable branch intended for production promotion.</li>
|
<li><code>main</code> is the stable branch intended for production promotion.</li>
|
||||||
<li>Feature work should start from <code>develop</code> and merge back into <code>develop</code>.</li>
|
<li>Feature work should start from <code>develop</code> and merge back into <code>develop</code>.</li>
|
||||||
|
<li>Customer release branches should start from <code>main</code> when a customer must not receive future feature work automatically.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@@ -93,6 +94,24 @@
|
|||||||
<li>promote <code>develop</code> into <code>main</code> when stable</li>
|
<li>promote <code>develop</code> into <code>main</code> when stable</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Customer release branches</h3>
|
||||||
|
<p>Use a dedicated release branch when a customer should receive the current stable product line but not future features by default.</p>
|
||||||
|
<ul>
|
||||||
|
<li>Example branch: <code>release/tubco-v1</code></li>
|
||||||
|
<li>Example baseline tag: <code>tubco-baseline-2026-03</code></li>
|
||||||
|
<li>Base the branch on the current <code>main</code>.</li>
|
||||||
|
<li>Continue product work on <code>develop</code>.</li>
|
||||||
|
<li>Only backport approved:
|
||||||
|
<ul>
|
||||||
|
<li>bug fixes</li>
|
||||||
|
<li>security updates</li>
|
||||||
|
<li>UI improvements</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>This keeps the customer line stable while the main product keeps evolving.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 id="local">4) Local Development Workflow</h2>
|
<h2 id="local">4) Local Development Workflow</h2>
|
||||||
<h3>Start</h3>
|
<h3>Start</h3>
|
||||||
@@ -366,6 +385,16 @@ make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS</code></pre>
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3>What is good enough today vs what is standard later</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Today:</strong> GitHub runs CI, then a LAN-connected Mac deploys to the private test server with the helper scripts.</li>
|
||||||
|
<li><strong>Standard later:</strong> GitHub runs CI, then an approved deploy job promotes tested code to staging and production automatically.</li>
|
||||||
|
<li><strong>Why they differ:</strong> the current test server is inside the LAN, so GitHub-hosted runners cannot reach it directly.</li>
|
||||||
|
<li><strong>When to upgrade:</strong> do it once staging or production lives on a public host, or once you add a self-hosted runner inside the LAN.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h3>Why deploy is manual right now</h3>
|
<h3>Why deploy is manual right now</h3>
|
||||||
<p>The test server is inside the local network and uses a private IP address <code>192.168.2.55</code>. GitHub-hosted runners on the public internet cannot reliably reach that target. Because of that, the correct deployment path today is:</p>
|
<p>The test server is inside the local network and uses a private IP address <code>192.168.2.55</code>. GitHub-hosted runners on the public internet cannot reliably reach that target. Because of that, the correct deployment path today is:</p>
|
||||||
@@ -377,6 +406,34 @@ make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS</code></pre>
|
|||||||
<p>Automatic CD from GitHub becomes appropriate only after moving to a public server or using a self-hosted runner inside the LAN.</p>
|
<p>Automatic CD from GitHub becomes appropriate only after moving to a public server or using a self-hosted runner inside the LAN.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3>Recommended standard CI/CD model</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Keep one private repository.</li>
|
||||||
|
<li>Use short-lived feature branches from <code>develop</code>.</li>
|
||||||
|
<li>Require CI to pass before merge into <code>develop</code>.</li>
|
||||||
|
<li>Deploy <code>develop</code> to a staging environment.</li>
|
||||||
|
<li>Promote <code>develop</code> into <code>main</code> only after staging validation.</li>
|
||||||
|
<li>Deploy <code>main</code> to production behind HTTPS with <code>DEBUG=0</code>.</li>
|
||||||
|
<li>Protect production with GitHub environment approvals, production secrets, and rollback steps.</li>
|
||||||
|
</ol>
|
||||||
|
<p>If you want the most standard shape, the long-term target is:</p>
|
||||||
|
<pre><code>feature branch -> CI -> develop -> staging deploy -> validate -> main -> production deploy</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3>What to change when this becomes a standard deployment</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Move staging and production onto hosts that GitHub or a self-hosted runner can reach reliably.</li>
|
||||||
|
<li>Keep separate env files and secrets for staging and production.</li>
|
||||||
|
<li>Run production with <code>DJANGO_DEBUG=0</code>, secure cookies, and HTTPS only.</li>
|
||||||
|
<li>Add GitHub environment protection rules for <code>development</code> and <code>production</code>.</li>
|
||||||
|
<li>Use the production deploy path only from <code>main</code>.</li>
|
||||||
|
<li>Add backup verification and health verification as standard post-deploy checks.</li>
|
||||||
|
<li>Later, consider image-based deploys if you want cleaner rollbacks than source-upload deploys.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h3>What to do for normal work</h3>
|
<h3>What to do for normal work</h3>
|
||||||
<ol>
|
<ol>
|
||||||
@@ -391,6 +448,15 @@ make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS</code></pre>
|
|||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3>Decision guide</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Keep the current manual Mac deploy flow</strong> if the server stays private inside the LAN.</li>
|
||||||
|
<li><strong>Use a self-hosted GitHub runner</strong> if you want automated deploys but the server must stay private.</li>
|
||||||
|
<li><strong>Use GitHub-hosted deploy jobs</strong> once staging or production runs on a publicly reachable host or VPN-reachable target.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h3>One-command test deployment</h3>
|
<h3>One-command test deployment</h3>
|
||||||
<p>From the Mac on the same network:</p>
|
<p>From the Mac on the same network:</p>
|
||||||
@@ -448,6 +514,17 @@ make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS</code></pre>
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3>Minimal standard checklist for later</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Staging and production must not share secrets.</li>
|
||||||
|
<li>Production deploy must run from <code>main</code> only.</li>
|
||||||
|
<li>Production must have HTTPS, health checks, backups, and a documented rollback path.</li>
|
||||||
|
<li>Runtime config sync must stay explicit. Code deploys should not silently overwrite DB-backed config.</li>
|
||||||
|
<li>Every deploy path should end with a health check and a quick browser verification of critical flows.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h3>If the local deploy helper fails</h3>
|
<h3>If the local deploy helper fails</h3>
|
||||||
<ol>
|
<ol>
|
||||||
@@ -624,6 +701,20 @@ make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS</code></pre>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 id="troubleshooting">20) Troubleshooting</h2>
|
<h2 id="troubleshooting">20) Troubleshooting</h2>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Localhost still looks stale after the server is already fixed</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Hard refresh the page with <code>Cmd + Shift + R</code>.</li>
|
||||||
|
<li>If it still looks wrong, clear site data for <code>127.0.0.1:8088</code> in the browser devtools and sign in again.</li>
|
||||||
|
<li>Restart the local web container:
|
||||||
|
<pre><code>docker compose restart web</code></pre>
|
||||||
|
</li>
|
||||||
|
<li>If the issue still survives, rebuild the local stack:
|
||||||
|
<pre><code>docker compose up -d --build</code></pre>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<p>This is the right order when shared header fixes, page-local CSS fixes, or versioned static assets look correct on the server but localhost still shows the old UI.</p>
|
||||||
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Page looks stale:</strong> restart <code>web</code> and hard-refresh browser</li>
|
<li><strong>Page looks stale:</strong> restart <code>web</code> and hard-refresh browser</li>
|
||||||
<li><strong>Second request hangs:</strong> inspect web logs and verify health endpoint</li>
|
<li><strong>Second request hangs:</strong> inspect web logs and verify health endpoint</li>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from workflows.app_registry import build_portal_app_sections, ensure_portal_app_configs
|
from workflows.app_registry import build_portal_app_sections, ensure_portal_app_configs
|
||||||
from workflows.models import PortalAppConfig
|
from workflows.models import PortalAppConfig
|
||||||
@@ -48,3 +49,17 @@ class AppRegistryPermissionTests(TestCase):
|
|||||||
self.assertNotIn('requests_dashboard', self._visible_keys(self.staff))
|
self.assertNotIn('requests_dashboard', self._visible_keys(self.staff))
|
||||||
self.assertIn('requests_dashboard', self._visible_keys(self.it_staff))
|
self.assertIn('requests_dashboard', self._visible_keys(self.it_staff))
|
||||||
|
|
||||||
|
def test_super_admin_cannot_open_platform_owner_app_registry_page(self):
|
||||||
|
self.client.force_login(self.super_admin)
|
||||||
|
|
||||||
|
response = self.client.get(reverse('portal_app_registry_page'))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, reverse('home'))
|
||||||
|
|
||||||
|
def test_platform_owner_can_open_app_registry_page(self):
|
||||||
|
self.client.force_login(self.platform_owner)
|
||||||
|
|
||||||
|
response = self.client.get(reverse('portal_app_registry_page'))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|||||||
Reference in New Issue
Block a user