Skip to content

Commit ca6c30c

Browse files
tensornullCometAPI
andauthored
fix: require live smoke opt-in (#7)
Co-authored-by: CometAPI <support@cometapi.com>
1 parent 17a1b67 commit ca6c30c

7 files changed

Lines changed: 61 additions & 28 deletions

File tree

.github/workflows/live-smoke.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
name: Bounded Chat Completions and Responses smoke
2828
if: >-
2929
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) &&
30-
(github.event_name == 'workflow_dispatch' || vars.LIVE_SMOKE_ENABLED == 'true')
30+
vars.LIVE_SMOKE_ENABLED == 'true'
3131
runs-on: ubuntu-latest
3232
timeout-minutes: 10
3333
environment: live-smoke

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ jobs:
9393
timeout-minutes: 10
9494
permissions:
9595
contents: read
96-
# Required repository configuration: protect this environment, require reviewers, and
97-
# configure COMETAPI_KEY plus the approved COMETAPI_LIVE_MODEL variable.
96+
# Required repository configuration: protect this environment without required reviewers,
97+
# and configure COMETAPI_KEY plus the approved COMETAPI_LIVE_MODEL variable.
9898
environment: live-smoke
9999
env:
100100
COMETAPI_LIVE_CONCURRENCY: "1"

AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ Before the first remote push:
4646
violations in one run while returning non-zero if any violation exists.
4747
Keep checks for canonical identity, contacts, repository metadata, public-
4848
safe language, and standalone content.
49-
4. Gate scheduled live smoke with a `LIVE_SMOKE_ENABLED` repository variable.
50-
An unset or non-true value must prevent live execution. Keep
49+
4. Gate scheduled and manually dispatched live smoke with a
50+
`LIVE_SMOKE_ENABLED` repository variable. An unset or non-true value must
51+
prevent live execution. Keep
5152
`RELEASE_PLEASE_ENABLED` disabled through the initial manual alpha.
5253
5. Make the release live-model setting use `gpt-5.4` when
5354
`COMETAPI_LIVE_MODEL` is unset or empty; never allow an empty model value.

RELEASING.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ The package manifest uses `authors = [{ name = "CometAPI" }]`. Remove
3939
`.github/CODEOWNERS` and its validation dependencies; it is not required while
4040
the project has one active maintainer.
4141

42-
Before the first push, require `LIVE_SMOKE_ENABLED=true` for scheduled live
43-
execution and keep `RELEASE_PLEASE_ENABLED` disabled through the initial manual
44-
alpha. An unset or non-true value prevents the corresponding workflow from
45-
running. The release live-model configuration resolves an unset or empty
46-
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.
42+
Before the first push, require `LIVE_SMOKE_ENABLED=true` for scheduled and
43+
manually dispatched live execution, and keep `RELEASE_PLEASE_ENABLED` disabled
44+
through the initial manual alpha. An unset or non-true value prevents the
45+
corresponding gated job from executing. The release live-model configuration
46+
resolves an unset or empty `COMETAPI_LIVE_MODEL` to `gpt-5.4`.
4747

4848
The private stage validates sanitized history, the complete local gate, and
4949
real credential-free default-branch CI only. Do not configure or exercise
@@ -128,8 +128,8 @@ violations in one run and still returns non-zero when any violation exists.
128128
is ongoing monitoring only and cannot satisfy a release gate. It is capped at
129129
four requests, 16 output tokens
130130
per generation, a 30-second request timeout, concurrency one, a ten-minute
131-
workflow timeout, and stop on the first failure. Scheduled execution also
132-
requires `LIVE_SMOKE_ENABLED=true`.
131+
workflow timeout, and stop on the first failure. Every trigger requires
132+
`LIVE_SMOKE_ENABLED=true`.
133133
- `release-please.yml` maintains a human-reviewed version and changelog pull
134134
request from Conventional Commits after maintainers enable the
135135
`RELEASE_PLEASE_ENABLED` repository variable. Keep it disabled until the

ROADMAP.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,9 @@ All workflow files must pass local `actionlint` 1.7.12. This is static
216216
validation only. Remote behavior remains unverified until each workflow runs
217217
successfully in the canonical GitHub repository.
218218

219-
Scheduled live smoke must additionally require `LIVE_SMOKE_ENABLED=true`; an
220-
unset or other value prevents live execution. Release Please requires
219+
Scheduled and manually dispatched live smoke must require
220+
`LIVE_SMOKE_ENABLED=true`; an unset or other value prevents live execution.
221+
Release Please requires
221222
`RELEASE_PLEASE_ENABLED=true` and remains disabled through the initial manual
222223
alpha. Release jobs must resolve an unset or empty `COMETAPI_LIVE_MODEL` to
223224
`gpt-5.4` rather than attempt a request with an empty model.

scripts/check_workflows.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ def _require_pattern(text: str, pattern: str, message: str) -> None:
2323
raise CheckError(message)
2424

2525

26-
def _job(text: str, name: str) -> str:
26+
def _job(text: str, name: str, *, source: str = "publish workflow") -> str:
2727
match = re.search(
2828
rf"(?ms)^ {re.escape(name)}:\n(?P<body>.*?)(?=^ [a-zA-Z0-9_-]+:\n|\Z)",
2929
text,
3030
)
3131
if match is None:
32-
raise CheckError(f"publish workflow has no {name!r} job")
32+
raise CheckError(f"{source} has no {name!r} job")
3333
return match.group(0)
3434

3535

@@ -135,20 +135,21 @@ def check_publish_workflow(text: str, live_smoke_text: str) -> None:
135135
if write_permissions != ["id-token"]:
136136
raise CheckError("id-token: write on the publish job must be the only write permission")
137137

138+
monitoring_live = _job(live_smoke_text, "smoke", source="live-smoke workflow")
138139
_require_pattern(
139140
live_smoke_text,
140141
r"(?m)^concurrency:\n group: trusted-live-smoke\n cancel-in-progress: false$",
141142
"release and monitoring live smokes must share one non-cancelling concurrency group",
142143
)
143-
_require(
144-
live_smoke_text,
145-
"(github.event_name == 'workflow_dispatch' || vars.LIVE_SMOKE_ENABLED == 'true')",
146-
"scheduled live smoke must require LIVE_SMOKE_ENABLED=true",
147-
)
148-
_require(
149-
live_smoke_text,
150-
"github.ref == format('refs/heads/{0}', github.event.repository.default_branch)",
151-
"monitoring live smoke must run only against the canonical default branch",
144+
_require_pattern(
145+
monitoring_live,
146+
r"(?m)^ if: >-\n"
147+
r" github\.ref == format\('refs/heads/\{0\}', "
148+
r"github\.event\.repository\.default_branch\) &&\n"
149+
r" vars\.LIVE_SMOKE_ENABLED == 'true'\n"
150+
r" runs-on:",
151+
"monitoring live smoke must run only against the canonical default branch and "
152+
"require LIVE_SMOKE_ENABLED=true for every trigger",
152153
)
153154

154155
build = _job(text, "build")

tests/test_release_workflow.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,13 +380,43 @@ def test_semantic_contract_rejects_split_monitoring_live_concurrency() -> None:
380380
)
381381

382382

383-
def test_semantic_contract_rejects_ungated_scheduled_live_smoke() -> None:
384-
live_smoke = LIVE_SMOKE_WORKFLOW.read_text(encoding="utf-8").replace(
383+
@pytest.mark.parametrize(
384+
"replacement",
385+
[
386+
"github.event_name == 'workflow_dispatch'",
385387
"(github.event_name == 'workflow_dispatch' || vars.LIVE_SMOKE_ENABLED == 'true')",
388+
"vars.LIVE_SMOKE_ENABLED != 'false'",
389+
"vars.LIVE_SMOKE_ENABLED == 'true'\n || github.event_name == 'workflow_dispatch'",
390+
],
391+
ids=["manual-only", "manual-bypass", "non-exact-opt-in", "continued-manual-bypass"],
392+
)
393+
def test_semantic_contract_rejects_live_smoke_gate_bypasses(replacement: str) -> None:
394+
live_smoke = LIVE_SMOKE_WORKFLOW.read_text(encoding="utf-8").replace(
395+
"vars.LIVE_SMOKE_ENABLED == 'true'",
396+
replacement,
397+
1,
398+
)
399+
with pytest.raises(RuntimeError, match="every trigger"):
400+
check_publish_workflow(
401+
PUBLISH_WORKFLOW.read_text(encoding="utf-8"),
402+
live_smoke,
403+
)
404+
405+
406+
def test_semantic_contract_checks_live_smoke_gate_on_smoke_job() -> None:
407+
live_smoke = LIVE_SMOKE_WORKFLOW.read_text(encoding="utf-8").replace(
408+
"vars.LIVE_SMOKE_ENABLED == 'true'",
386409
"github.event_name == 'workflow_dispatch'",
387410
1,
388411
)
389-
with pytest.raises(RuntimeError, match="LIVE_SMOKE_ENABLED=true"):
412+
live_smoke += """
413+
decoy:
414+
if: >-
415+
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) &&
416+
vars.LIVE_SMOKE_ENABLED == 'true'
417+
runs-on: ubuntu-latest
418+
"""
419+
with pytest.raises(RuntimeError, match="every trigger"):
390420
check_publish_workflow(
391421
PUBLISH_WORKFLOW.read_text(encoding="utf-8"),
392422
live_smoke,

0 commit comments

Comments
 (0)