mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 17:51:58 +01:00
fix(evals): pass named environment secrets to case workflows (#18680)
## Thinking Path > - Paperclip lets people manage AI agents and inspect their work. > - The chat catalog sends each original Product E2E result to TestOps. > - Each case uses a reusable workflow with separate protected job environments. > - The caller passed literal empty strings for three secret names. > - A live worker received an empty model key although its environment had that secret. > - GitHub requires matching named secret references at the caller boundary. > - This PR uses those references and keeps the closed credential boundaries. ## Linked Issues or Issue Description Refs #18647, #18666, #18676. **What happened?** The first two workers in a live streaming check failed before a qualified native case. Their model-key environment values were empty. Name-only metadata confirmed that the key exists in the intended protected environment. Both closed diagnostics recorded zero runs, zero checks and no original result carrier. The remaining case was cancelled before its original execution. These records are setup failures, not feature or model grades. **Expected behavior** Each caller passes only the three declared secret names through matching expressions. Each called job uses its own protected environment and only its existing credential consumers. GitHub documents this requirement even when the secret exists only in that environment: [Reuse workflows](https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows). **Steps to reproduce** Run the new `paidWorker` consumer control against the parent helper. It rejects the supported named-reference caller. The deployed literal-empty caller also has the retained live empty-key witness. ## What Changed - Replace six literal empty values in the two case callers with exact matching secret references. - Keep the host-contract resolver closed to those three names and expressions. - Reject blank, extra, mismatched and inherited credentials in focused consumer and workflow controls. Keep the existing lane and authorization controls. - Keep the called workflow, protected environments and credential-consuming steps unchanged. No secret was read, copied, rotated or added during this repair. ## Verification - Parent consumer control: RED with `Workflow caller boundary mismatch` for the named-reference contract. - `node --test tests/runner-e2e/chat-completion-testops-contract.test.mjs tests/runner-e2e/chat-catalog-testops-streaming.test.mjs`: 36 controls pass. - Review found an existing streaming-test mutation that became a no-op after the caller contract changed. Its actual `Missing expected exception` failure is retained. The mutation now uses a blank value, and its description names the current contract. All production posts are unchanged by this test correction. - The focused `Runner eval workflows pin actions and gate paid live execution` security control passes. - Four JavaScript syntax checks, actionlint for caller and callee, and `git diff --check` pass. - An independent source peer checked the four original frozen posts and unchanged callee. The two-line dependent-test correction has a separate peer review. - Local checks use synthetic values, source files and mocked boundaries. No local app, database, native build or provider ran. - Corrected-head GitHub CI passes with 48 successful jobs and one skipped. Both required aggregates, typecheck, build and browser checks pass. This head needed no CI recovery. - Live nonempty resolution and incremental TestOps publication still need qualification after delivery. ## Risks - The previous assumption that literal empty placeholders would resolve environment secrets was wrong in the observed run. This change follows the current primary contract. Local controls do not prove GitHub runtime resolution. - The three names stay explicit. There is no `secrets: inherit`, new secret, environment change or permission expansion. The model worker, private reader and TestOps uploader keep their existing separate consumers. - The immutable evaluation source, cases, original grades, run limits, deadlines and zero-retry policy do not change. Earlier setup failures remain recorded. The existing large batch is not changed or replayed. - Streaming publication and final backend coverage remain unqualified until a real small run proves them. ## Model Used OpenAI `gpt-6.1-sol`, with tool use and code execution. The session does not expose a context-window size. The independent source peer uses the same model family. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d0ffa179b4
commit
3f8ebfff8a
5 files changed
+44
-13
No files matched your search
@@ -643,7 +643,8 @@ test("Runner eval workflows pin actions and gate paid live execution", () => {
|
||||
if (name === "chat-catalog-testops.yml") {
|
||||
assert.match(triggerHeader, /^ workflow_dispatch:/m);
|
||||
assert.doesNotMatch(triggerHeader, /^ (?:push|pull_request|workflow_call|workflow_run):/m);
|
||||
assert.doesNotMatch(workflow, providerSecretReference);
|
||||
const callerBlocks = ["chat", "chat_external"].map(id => workflow.split(` ${id}:\n`)[1].split(/\n [a-z_]+:\n/)[0]);
|
||||
assert.doesNotMatch(callerBlocks.reduce((rest, block) => rest.replace(block, ""), workflow), providerSecretReference);
|
||||
assert.match(workflow, /group: chat-catalog-testops-streaming-v1/);
|
||||
for (const id of ["chat", "chat_external"]) {
|
||||
const block = workflow.split(` ${id}:\n`)[1].split(/\n [a-z_]+:\n/)[0];
|
||||
@@ -653,14 +654,23 @@ test("Runner eval workflows pin actions and gate paid live execution", () => {
|
||||
assert.match(block, /needs: \[authorize, select, build\]/);
|
||||
assert.match(block, /uses: \.\/\.github\/workflows\/chat-catalog-testops-case\.yml/);
|
||||
assert.match(block, /protocol: streaming-v1/);
|
||||
assert.match(block, /secrets:\n OPENROUTER_API_KEY: ''\n ANTHROPIC_API_KEY: ''\n ALLURE_TOKEN: ''/);
|
||||
assert.doesNotMatch(block, /secrets: inherit|secrets\./);
|
||||
const mapping = block.split("\n secrets:\n");
|
||||
assert.equal(mapping.length, 2);
|
||||
assert.deepEqual(mapping[1].trim().split("\n").map(line => line.trim()), [
|
||||
"OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}",
|
||||
"ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}",
|
||||
"ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}",
|
||||
]);
|
||||
assert.doesNotMatch(block, /secrets: inherit/);
|
||||
assert.match(block, /fail-fast: false/);
|
||||
};
|
||||
assertCaller(block);
|
||||
const lane = id === "chat" ? "native" : "external";
|
||||
for (const mutant of [block.replace("needs.authorize.outputs.operation == 'evaluate' && ", ""),
|
||||
block.replace(`needs.select.outputs.${lane}_count != '0'`, 'true')]) assert.throws(() => assertCaller(mutant));
|
||||
block.replace(`needs.select.outputs.${lane}_count != '0'`, 'true'),
|
||||
block.replace('${{ secrets.OPENROUTER_API_KEY }}', "''"),
|
||||
block + '\n EXTRA: ${{ secrets.EXTRA }}',
|
||||
block.replace(/\n secrets:[\s\S]*/, '\n secrets: inherit')]) assert.throws(() => assertCaller(mutant));
|
||||
}
|
||||
const final = workflow.split(" verify:\n")[1];
|
||||
assert.match(final, /chat-completion-testops-cli\.mjs verify-stream/);
|
||||
|
||||
Reference in new issue
Block a user