fix: keep staging control plane on main
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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|=)/);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user