fix: keep staging control plane on main

This commit is contained in:
Jesse_Chen
2026-07-21 07:45:14 +08:00
parent bd7c9830bb
commit 9e119df977
11 changed files with 100 additions and 27 deletions
@@ -7,6 +7,8 @@ grant usage on schema identity to identity_runtime, admin_runtime;
grant usage on schema public to app_runtime, admin_runtime;
grant usage on schema audit to admin_runtime;
alter default privileges for role schema_owner
revoke execute on functions from public;
alter default privileges for role schema_owner in schema identity
revoke all on tables from public;
alter default privileges for role schema_owner in schema public
+17 -1
View File
@@ -373,13 +373,16 @@ test("foundation grants no direct runtime table DML and exposes only reviewed fu
create table audit.admin_event_probe (
value text not null
);
create function identity.unreviewed_identity_probe()
returns text
language sql
as 'select ''not callable''::text';
create function audit.record_admin_event_probe(event_value text)
returns void
language sql
security definer
set search_path = pg_catalog, audit
as 'insert into audit.admin_event_probe(value) values (event_value)';
revoke all on function audit.record_admin_event_probe(text) from public;
grant execute on function audit.record_admin_event_probe(text) to admin_runtime;
`,
);
@@ -412,6 +415,19 @@ test("foundation grants no direct runtime table DML and exposes only reviewed fu
"update public.runtime_boundary_probe set value = 'denied'",
),
);
assert.equal(
fixture.psql(
"select has_function_privilege('identity_runtime', 'identity.unreviewed_identity_probe()', 'execute')",
),
"f",
);
assert.throws(() =>
fixture.psqlAs(
"identity_runtime",
"identity-runtime-test-password",
"select identity.unreviewed_identity_probe()",
),
);
assert.equal(
fixture.psqlAs(
+11 -2
View File
@@ -132,6 +132,10 @@ test("staging deploy consumes only the isolated staging environment and tested r
new URL("../../.github/workflows/deploy-staging.yml", import.meta.url),
"utf8",
);
const syncController = readFileSync(
new URL("../../deploy/sync-staging-tree.sh", import.meta.url),
"utf8",
);
assert.match(qualityGate, /push:\s*\n\s*branches: \[staging\]/);
assert.match(workflow, /workflows: \["Staging Backend Quality Gate"\]/);
@@ -154,13 +158,18 @@ test("staging deploy consumes only the isolated staging environment and tested r
assert.match(workflow, /test "\$DEPLOY_HOST" = "118\.26\.111\.127"/);
assert.match(workflow, /test "\$DEPLOY_USER" = "deploy"/);
assert.match(workflow, /test "\$DEPLOY_PATH" = "\/opt\/jyotisha-staging"/);
assert.match(workflow, /--exclude='\/\.env\*'/);
assert.match(workflow, /--exclude='\/backups\/'/);
assert.match(
workflow,
/--include='\/deploy\/' --include='\/deploy\/\*\*\*' --exclude='\*'/,
);
assert.match(workflow, /run-staging-deploy\.sh/);
assert.match(workflow, /steps\.images\.outputs\.api_image/);
assert.match(workflow, /steps\.images\.outputs\.web_image/);
assert.doesNotMatch(workflow, /PRODUCTION_SSH_PRIVATE_KEY/);
assert.doesNotMatch(workflow, /103\.117\.123\.53/);
assert.match(syncController, /--exclude='\/\.env\*'/);
assert.match(syncController, /--exclude='\/\.docker\/'/);
assert.match(syncController, /--exclude='\/backups\/'/);
});
test("staging env validator rejects selector drift, duplicates, and unsafe permissions", () => {
@@ -92,6 +92,8 @@ test("live staging sync preserves env, state, incoming files, and encrypted back
mkdirSync(source);
mkdirSync(destination);
writeFileSync(join(source, "revision.txt"), "new\n");
mkdirSync(join(source, ".docker"));
writeFileSync(join(source, ".docker", "config.json"), "temporary-token\n");
writeFileSync(join(destination, "stale.txt"), "old\n");
for (const relative of [
".env.staging",
@@ -114,6 +116,7 @@ test("live staging sync preserves env, state, incoming files, and encrypted back
assert.equal(result.status, 0, result.stderr);
assert.equal(existsSync(join(destination, "stale.txt")), false);
assert.equal(readFileSync(join(destination, "revision.txt"), "utf8"), "new\n");
assert.equal(existsSync(join(destination, ".docker")), false);
for (const relative of [
".env.staging",
".env.staging.database",
@@ -155,6 +158,7 @@ test("deploy and migration consume the exact successful gate artifact", () => {
assert.match(workflow, /\.head_branch == "staging"/);
assert.match(workflow, /\.event == "push"/);
assert.match(workflow, /\.conclusion == "success"/);
assert.match(workflow, /sort_by\(\.id\) \| reverse \| first/);
assert.match(workflow, /uses: actions\/download-artifact@v4/);
assert.match(workflow, /run-id: \$\{\{ steps\.revision\.outputs\.gate_run_id \}\}/);
assert.match(workflow, /node frontend\/scripts\/staging-image-manifest\.mjs/);
@@ -162,6 +166,25 @@ test("deploy and migration consume the exact successful gate artifact", () => {
}
});
test("main owns the deployment control plane and target revisions are data only", () => {
for (const workflow of [read(deployWorkflow), read(migrationWorkflow)]) {
assert.match(workflow, /name: Checkout trusted main controller[\s\S]*ref: main/);
assert.match(workflow, /fetch-depth: 0/);
assert.match(workflow, /git merge-base --is-ancestor "\$DEPLOY_SHA" HEAD/);
assert.match(workflow, /--include='\/deploy\/' --include='\/deploy\/\*\*\*' --exclude='\*'/);
assert.doesNotMatch(workflow, /ref: \$\{\{ steps\.revision\.outputs\.sha \}\}/);
}
});
test("staging mutations retain every pending deployment and migration", () => {
for (const workflow of [read(deployWorkflow), read(migrationWorkflow)]) {
assert.match(
workflow,
/concurrency:\n group: staging-mutation\n cancel-in-progress: false\n queue: max/,
);
}
});
test("automatic staging paths reject stale and divergent revisions", () => {
const deployment = read(deployWorkflow);
const migration = read(migrationWorkflow);
@@ -186,6 +209,10 @@ test("remote deployment verifies running image IDs, RepoDigests, and application
assert.match(runner, /publicBody\.deployment\?\.gitCommit !== process\.env\.EXPECTED_SHA/);
assert.match(runner, /mv -f "\$revision_file" "\$state_directory\/deployed-revision"/);
assert.match(runner, /restoring prior image digests/);
assert.match(
runner,
/switched=true\n"\$\{compose\[@\]\}" up -d --no-build --remove-orphans\n/,
);
assert.doesNotMatch(runner, /jyotisha-(?:api|web):\$DEPLOY_SHA/);
});
@@ -223,6 +250,10 @@ test("run-local registry state and incoming trees are always cleaned up", () =>
assert.match(workflow, /if: always\(\) && steps\.incoming\.outputs\.path != ''/);
assert.match(workflow, /docker logout ghcr\.io/);
assert.match(workflow, /rm -rf -- '\$INCOMING_PATH'/);
assert.match(
workflow,
/install -d -m 700 [^\n]*\$incoming[^\n]*\n\s+echo "path=\$incoming" >>"\$GITHUB_OUTPUT"\n\s+rsync/,
);
assert.doesNotMatch(workflow, /--password(?:\s|=)/);
}
});