fix(ci): OPENAI_BASE_URL is optional, and an Actions variable not a secret - #351
Merged
Conversation
…n a secret
Owner rulings, 2026-09-07, mirrored from the same change in
amplifier-bundle-dot-runner:
1. OPENAI_BASE_URL is OPTIONAL. Absent, the `luna` instance runs on the
OpenAI module's own default endpoint ("the default should have been
fine"). The preflight must not refuse for its absence. It still refuses
for a missing OPENAI_API_KEY and for an instance nothing can serve --
the issue-#155 / EXTENSIONS Sec 36 guarantee that no run starts unable
to reach its second judge is untouched.
2. It is NOT a secret. It is an endpoint URL, now set as a repository
VARIABLE on both repos, so every workflow reads
`${{ vars.OPENAI_BASE_URL || secrets.OPENAI_BASE_URL }}` -- vars first,
secrets kept as a fallback so an operator who stored it as a secret
before the ruling is not broken.
.github/capsule-pipeline/provider-instances.yaml
The header now separates REQUIRED (OPENAI_API_KEY, a secret) from OPTIONAL
(OPENAI_BASE_URL, a variable), and carries the machine-readable half of
that split: an `optional-vars:` directive the installer reads. The prose
and the check can no longer disagree, which is the failure mode a
documented-only "optional" always ends in.
.github/capsule-pipeline/install_provider_instances.sh
Reads that directive. A required var missing is a refusal, as before. An
optional var missing takes its whole `key: ${VAR}` line OUT of the
installed file -- so the module default applies -- and the step log says
which name was omitted and why, because "the operator chose the default"
and "the endpoint silently went missing" must not look the same in a log.
Two new refusals rather than guesses: a directive naming a placeholder the
template does not reference (exit 2), and an unset optional var still
referenced after the line deletion, i.e. in a shape too tangled to remove
surgically (exit 2 -- the engine leaves an unexpandable ${VAR} verbatim,
which in a base_url would send the run at a literal host).
.github/workflows/{capsule-specify,feature-specify,capsule-implement}.yml
All 14 OPENAI_BASE_URL assignments (preflight, engine run, and every
scan/scrub/gate step) take the vars-then-secrets expression. Each
preflight step's comment states the split: which name is a secret and
required, which is a variable and optional, and what the empty string --
what the expression yields when neither exists -- makes the installer do.
tests/test_provider_base_url_optional.py
New, and RED before this commit (5 of 11 failing). Runs the real installer
in a throwaway HOME with an explicitly built environment, so a developer
who exports OPENAI_BASE_URL cannot colour the result:
- unset AND set-but-empty (the shape CI actually produces: Actions sets
the env entry to "" rather than dropping it) install the instance with
no base_url key, exit 0, and log the omission;
- the omission is surgical -- the installed file is the template minus
exactly its one base_url line, byte for byte;
- set, the key is written as the ${VAR} PLACEHOLDER; neither the URL nor
the api key is ever expanded onto disk;
- a missing OPENAI_API_KEY still refuses, names that key, and does not
mention OPENAI_BASE_URL -- a refusal that sends an operator to
provision the wrong thing is worse than no message;
- an instance the template does not define is still refused;
- every workflow assignment matches the vars-then-secrets expression
exactly (fail-closed if a file sets it nowhere);
- the scrubber ignores an EMPTY watched value, with a positive control
that a real value is still redacted -- an empty literal matches at
every position in every file, so an unguarded watch list would shred
the whole evidence tree the moment the endpoint stopped being set.
Verified: actionlint clean on all three workflows (the only two findings are
pre-existing SC2012 `ls` infos, present identically on HEAD); shellcheck
clean on the installer; PyYAML parses every workflow and the installed
settings file in both branches.
Generated with Amplifier
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…claims, append the record
The rule this repo already applies (see the 2026-09-07 records commit): a dated
record keeps its words; a live claim gets corrected or marked past.
.github/capsule-pipeline/README.md
- The `provider-instances.yaml` row said "Required repo secrets:
OPENAI_API_KEY, OPENAI_BASE_URL". It now states the two different shapes:
one required secret, one optional variable whose absence means the module's
default endpoint, plus what the `optional-vars:` directive is for.
- The `install_provider_instances.sh` row gains the optional half of the
derivation (nothing is written down twice: instance ids from the graphs,
credentials from the template, optionality from its directive) and the two
new refusals, so its "three refusals" count is no longer wrong.
- A new re-sync entry carries the two rulings, names the earlier entry's
superseded line rather than editing it, and records the new guard.
docs/designs/REVIEW-pipelines-2026-09.md
Sec 4.4's addendum ends with "New repo secrets required: OPENAI_API_KEY,
OPENAI_BASE_URL". Half of that is now wrong, so it gets a second addendum
rather than an edit -- the same treatment the first one got. Sec 4.4's core
claim is untouched: a luna pin still cannot be smuggled in without its infra.
.github/capsule-pipeline/task-runner.dot
Its 2026-09-07 header bullet read "the credential the run needs
(OPENAI_API_KEY / OPENAI_BASE_URL, not OPENAI_API_KEY)" -- a same-day rename
had left the same variable on both sides of a contrast, so the sentence said
nothing. Now: one secret, OPENAI_API_KEY, and an optional variable. Header
comment only; the graph body and its pinned sha are untouched, and the file
still renders under `dot -Tsvg`.
.github/capsule-pipeline/scrub_secrets.py
Comments only. The watch-list note records that OPENAI_BASE_URL is a
variable, is optional, and that an UNSET one must be a no-op here rather than
a scrub; MIN_LITERAL_LEN's note records that its floor case is what makes
that true -- an empty literal matches at every position in every file, so the
unguarded shape would not leak evidence, it would destroy it.
tests/test_no_live_gpt5_pins.py
Its instance-provisioning guard demanded `secrets.<NAME>` for every ${VAR}
the template references. That is now wrong for an Actions variable, so it
accepts `vars.<NAME>` or `secrets.<NAME>` -- the exact expression stays
pinned by tests/test_provider_base_url_optional.py, which is the file that
should own it.
Verified: full root suite green (241 passed, 2 pre-existing skips);
`dot -Tsvg` renders task-runner.dot; the scrubber's own suite green
(33 passed, 103 subtests).
Generated with Amplifier
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…ner uses The sibling change in amplifier-bundle-dot-runner (PR #86) logs "<NAME> is set" / "<NAME> is unset" from its inline preflight. This repo's preflight is a script over a template rather than a heredoc, so the two implementations differ by construction -- but the job log a human greps should not. The installer now prints one line per OPTIONAL var in those same words, and the guard pins the phrase rather than a paraphrase of it. Also verified against that PR: the workflow expression is character-identical in both repos -- `${{ vars.OPENAI_BASE_URL || secrets.OPENAI_BASE_URL }}`. Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mirror of
microsoft/amplifier-bundle-dot-runner#86, for this repo's owncapsule pipelines. Two owner rulings, 2026-09-07:
OPENAI_BASE_URLis OPTIONAL. Absent, thelunainstance runs on theprovider-openaimodule's own default endpoint -- "the default should havebeen fine". The preflight must not refuse for its absence.
VARIABLE on both repos, so every workflow reads
${{ vars.OPENAI_BASE_URL || secrets.OPENAI_BASE_URL }}-- variable first,secret kept as a fallback so anyone who provisioned it as a secret is not
broken. Character-identical to the expression in dot-runner#86.
What still refuses, unchanged
The issue-#155 / EXTENSIONS §36 guarantee is untouched: no run starts unable
to reach its second judge. The preflight still refuses, before any of the
30-90+ minute budget is spent, on
OPENAI_API_KEY(still a repo SECRET, still REQUIRED), andprovider-instances.yamldoes not define.Nothing was weakened to make the endpoint optional. Two new refusals were
added, because the mechanism has its own failure modes: an
optional-vars:directive naming a placeholder the template never references, and an unset
optional var still referenced after the line deletion -- i.e. in a shape too
tangled to remove surgically. An unexpandable
${VAR}is left VERBATIM by theengine, which in a
base_urlwould aim the run at a literal host.How "optional" is expressed
provider-instances.yamldeclares it, in a machine-readable directive theinstaller reads:
# optional-vars: OPENAI_BASE_URLinstall_provider_instances.shalready derived the instance ids from thegraphs and the credential names from the template, so that neither list is
written down twice; optionality now comes from the same place. Unset, the whole
base_url: ${OPENAI_BASE_URL}line is left out of the installed file and thestep log says which branch the run took, in the same words dot-runner's inline
preflight uses:
"The operator chose the default" and "the endpoint silently went missing" must
not read the same in a job log.
The scrubber
OPENAI_BASE_URLstays on the literal watch list -- an endpoint's valuecarries no debugging value in evidence. What matters now is the EMPTY case,
which this change makes normal: an empty literal matches at every position in
every file, so an unguarded watch list would not leak the evidence tree, it
would destroy it.
MIN_LITERAL_LENalready prevented that; nothing pinned itand nothing said why it mattered. Now both, with a positive control so the
empty-value test cannot pass vacuously.
Tests -- RED first
tests/test_provider_base_url_optional.pyis new and was RED before thechange, 5 of 11 failing on the unmodified tree:
It runs the SHIPPED installer in a throwaway HOME with an explicitly built
environment, so a developer who exports
OPENAI_BASE_URLcannot colour theresult. It covers unset and set-but-empty -- the shape CI actually
produces, since Actions sets the env entry to
""rather than dropping it --proves the omission is surgical (installed file == template minus exactly that
one line, byte for byte), proves neither the URL nor the key is ever expanded
onto disk, keeps the two surviving refusals locked, and pins the workflow
expression fail-closed.
Verification
python -m pytest tests/ -q --ignore=tests/e2e-> 241 passed, 2 skipped(both skips pre-existing).
actionlint(with shellcheck) on all three changed workflows: the onlyfindings are two pre-existing
SC2012infos, present identically onmain.shellcheckclean oninstall_provider_instances.sh; PyYAML parses everyworkflow and the installed settings file in both branches.
dot -Tsvgrenderstask-runner.dotafter its header-comment fix.Records
A dated record keeps its words; a live claim gets corrected. The pipeline
README's Files rows and
install_provider_instances.sh's refusal count arecorrected in place; the "New repo secrets required:
OPENAI_API_KEY,OPENAI_BASE_URL" lines in the README's re-sync log and indocs/designs/REVIEW-pipelines-2026-09.md§4.4 are left verbatim andsuperseded by an appended entry / a second addendum.
task-runner.dot's headerbullet had been left by a same-day rename saying "OPENAI_API_KEY /
OPENAI_BASE_URL, not OPENAI_API_KEY" -- the same variable on both sides of a
contrast -- and now says what it meant to.
Generated with Amplifier
Co-Authored-By: Amplifier 240397093+microsoft-amplifier@users.noreply.github.com