Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .castiron.stats.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
schema_version: 1
generation_id: 592b2cad-c398-46a9-9df6-7858c09f3b65
openapi_spec_hash: d6717da751735789c482f47d6613cea0
openapi_transformed_spec_hash: 46e4f19c824b10d620e3c7bc4a7d569e
config_hash: 0fe55890f9499fed76b60662804e30f7
codegen_sha: f028b1b011d31cf0cd8a1939e83bf7ec5d2b8762
codegen_hash: 2e485073ea1166bc85b3b8c04a512d221b82ef5c490801250ffc209d6292e201
public_codegen_sha: b045ae7e94f055f952b812dfc4ddee314a691df7
generation_id: c6c71409-c9d1-487b-ad35-c8973d3274a6
openapi_spec_hash: 23f0bfa58ad53fef090bea6a72a05dda
openapi_transformed_spec_hash: 39f2336a30336863627b56624b390f24
config_hash: 249b5f41fe1b916955299cea75cddfbf
codegen_sha: 73e15aa409d55f8731c775cab5a85c9e93d66b3c
codegen_hash: 166545ad5fafe31085bb865842a68c605eea009ccfec4e4247b851e46d7c34fb
public_codegen_sha: 74fb1f948524997714d02fb52f387e0ab93ce338
80 changes: 74 additions & 6 deletions .github/workflows/castiron-custom-code-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,21 @@ jobs:
pull-requests: read
statuses: write
steps:
# Trusted main definition: no candidate checkout, artifacts, or executable inputs.
# Execute only the helper belonging to this immutable workflow revision.
- name: Check out the trusted review policy
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false

- name: Re-evaluate current PR and queue after review changes
env:
GH_TOKEN: ${{ github.token }}
GIT_CONFIG_COUNT: '2'
GIT_CONFIG_KEY_0: credential.helper
GIT_CONFIG_VALUE_0: ''
GIT_CONFIG_KEY_1: credential.https://github.com.helper
GIT_CONFIG_VALUE_1: '!gh auth git-credential'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
Expand All @@ -48,12 +61,37 @@ jobs:
run.head_sha !== signal.head_sha || run.path.split('@', 1)[0] !== '.github/workflows/castiron-custom-code.yml') {
throw new Error('Unexpected or superseded review signal');
}
async function mergeBase(base, head) {
const {data: comparison} = await github.rest.repos.compareCommitsWithBasehead({
...context.repo, basehead: `${base}...${head}`, per_page: 1,
});
const sha = comparison.merge_base_commit?.sha;
if (!/^[0-9a-f]{40}$/.test(sha || '')) throw new Error('Invalid policy merge base');
return sha;
}
async function requiresHuman(pr, mainSha) {
const path = require('node:path');
const fs = require('node:fs');
const root = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'castiron-review-'));
try {
const result = await exec.getExecOutput('python3', ['-I',
'scripts/castiron/custom_code_policy.py', 'review-required',
'--repository', repo, '--repo', path.join(root, 'objects.git'),
'--base', pr.base.sha, '--head', pr.head.sha, '--main', mainSha], {silent: true});
const decision = JSON.parse(result.stdout);
if (typeof decision.requires_human !== 'boolean') throw new Error('Invalid review decision');
return decision.requires_human;
} finally {
fs.rmSync(root, {recursive: true, force: true});
}
}
const dirtyTargets = new Set();
// Retry discovery within this job timeout while targets are changing.
while (true) {
const candidates = [];
let pulls = run.pull_requests?.length ? run.pull_requests : await github.paginate(
github.rest.repos.listPullRequestsAssociatedWithCommit, {...context.repo, commit_sha: run.head_sha});
const hasAssociation = pulls.length > 0;
if (!pulls.length) pulls = await github.paginate(github.rest.pulls.list, {
...context.repo, state: 'open', head: `${run.head_repository.owner.login}:${run.head_branch}`,
});
Expand All @@ -62,18 +100,48 @@ jobs:
if (pr.state !== 'open' || pr.base.ref !== 'next' || pr.base.repo.full_name !== repo ||
(item.head?.sha && pr.head.sha !== item.head.sha) || !/^[0-9a-f]{40}$/.test(pr.head.sha)) continue;
if (run.head_sha !== pr.head.sha) {
const {data: merge} = await github.rest.git.getRef({...context.repo, ref: `pull/${pr.number}/merge`});
const {data: commit} = await github.rest.repos.getCommit({...context.repo, ref: run.head_sha});
if (merge.object.sha !== run.head_sha || commit.parents.length !== 2 ||
commit.parents[0].sha !== pr.base.sha || commit.parents[1].sha !== pr.head.sha) continue;
if (commit.parents.length !== 2 || commit.parents[1].sha !== pr.head.sha ||
!/^[0-9a-f]{40}$/.test(commit.parents[0].sha) ||
await mergeBase(commit.parents[0].sha, pr.base.sha) !== commit.parents[0].sha) continue;
// Without a commit association, the live merge ref must identify this PR.
if (!hasAssociation) {
const {data: merge} = await github.rest.git.getRef({...context.repo, ref: `pull/${pr.number}/merge`});
if (merge.object.sha !== run.head_sha || commit.parents[0].sha !== pr.base.sha) continue;
}
}
candidates.push(pr);
}
if (!candidates.length) return; // Main, closed, and superseded PR reviews have no next target.
if (candidates.length !== 1) throw new Error('Review signal identifies ambiguous next PRs');
const pr = candidates[0];
// Commit associations may include other PRs sharing the same head.
const matching = candidates.length === 1 ? candidates : candidates.filter(pr =>
Number.isSafeInteger(run.head_repository?.id) && run.head_repository.id > 0 &&
pr.head.repo?.id === run.head_repository.id && typeof run.head_branch === 'string' &&
run.head_branch.length > 0 && pr.head.ref === run.head_branch);
if (matching.length !== 1) throw new Error('Review signal identifies ambiguous next PRs');
const pr = matching[0];
const files = await github.paginate(github.rest.pulls.listFiles, {...context.repo, pull_number: pr.number});
if (!files.some(file => file.filename === '.castiron-ratchet.json')) return;
const {data: main} = await github.rest.git.getRef({...context.repo, ref: 'heads/main'});
const mainSha = main.object.sha;
let requiresReview;
try {
requiresReview = await requiresHuman(pr, mainSha);
} catch (error) {
const {data: currentMain} = await github.rest.git.getRef({...context.repo, ref: 'heads/main'});
if (currentMain.object.sha !== mainSha) continue;
throw error;
}
if (!requiresReview) {
const [{data: current}, {data: target}, main] = await Promise.all([
github.rest.pulls.get({...context.repo, pull_number: pr.number}),
github.rest.git.getRef({...context.repo, ref: 'heads/next'}),
mainSha && github.rest.git.getRef({...context.repo, ref: 'heads/main'}),
]);
if (current.head.sha !== pr.head.sha || current.base.sha !== pr.base.sha ||
target.object.sha !== pr.base.sha || (main && main.data.object.sha !== mainSha)) continue;
return;
Comment on lines +135 to +143

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Re-run still-current dirty targets before this return. An iteration can mark the PR head pending, then retry because next advanced. If that base update adopts the proposed policy pin, requiresReview becomes false while the PR head stays unchanged. This branch returns without dispatching the prior reruns, leaving that head pending indefinitely. Keep the existing authenticated target/rerun path for still-current dirtyTargets, even when no new human review is needed. Add a regression with unchanged PR head, changed base pin, and a true-to-false policy decision; do not rerun obsolete queue heads.

}
// Read authoritative reviews, including edits: submittedAt alone misses
// removal/restoration of an attestation. Never trust candidate event artifacts.
let reviewCursor = null;
Expand Down
46 changes: 45 additions & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,13 @@ Methods:
Types:

```python
from openai.types.audio import Voice
from openai.types.audio import Voice, VoiceDeleted
```

Methods:

- <code title="post /audio/voices">client.audio.voices.<a href="./src/openai/resources/audio/voices.py">create</a>(\*\*<a href="src/openai/types/audio/voice_create_params.py">params</a>) -> <a href="./src/openai/types/audio/voice.py">Voice</a></code>
- <code title="delete /audio/voices/{voice_id}">client.audio.voices.<a href="./src/openai/resources/audio/voices.py">delete</a>(voice_id) -> <a href="./src/openai/types/audio/voice_deleted.py">VoiceDeleted</a></code>

## VoiceConsents

Expand Down Expand Up @@ -637,6 +638,49 @@ Methods:
- <code title="get /agents">client.beta.agents.<a href="./src/openai/resources/beta/agents/agents.py">list</a>(\*\*<a href="src/openai/types/beta/agent_list_params.py">params</a>) -> <a href="./src/openai/types/beta/agent.py">SyncCursorPage[Agent]</a></code>
- <code title="delete /agents/{agent_id}">client.beta.agents.<a href="./src/openai/resources/beta/agents/agents.py">delete</a>(agent_id) -> <a href="./src/openai/types/beta/agent_deleted.py">AgentDeleted</a></code>

### Automations

Types:

```python
from openai.types.beta.agents import (
Automation,
AutomationAgentConfig,
AutomationCreatedBy,
AutomationDeleted,
AutomationEnvironment,
AutomationMetadataParam,
AutomationTarget,
AutomationTargetParam,
OneTimeAutomation,
PeriodicAutomation,
)
```

Methods:

- <code title="post /agents/automations">client.beta.agents.automations.<a href="./src/openai/resources/beta/agents/automations/automations.py">create</a>(\*\*<a href="src/openai/types/beta/agents/automation_create_params.py">params</a>) -> <a href="./src/openai/types/beta/agents/automation.py">Automation</a></code>
- <code title="get /agents/automations/{automation_id}">client.beta.agents.automations.<a href="./src/openai/resources/beta/agents/automations/automations.py">retrieve</a>(automation_id) -> <a href="./src/openai/types/beta/agents/automation.py">Automation</a></code>
- <code title="post /agents/automations/{automation_id}">client.beta.agents.automations.<a href="./src/openai/resources/beta/agents/automations/automations.py">update</a>(automation_id, \*\*<a href="src/openai/types/beta/agents/automation_update_params.py">params</a>) -> <a href="./src/openai/types/beta/agents/automation.py">Automation</a></code>
- <code title="get /agents/automations">client.beta.agents.automations.<a href="./src/openai/resources/beta/agents/automations/automations.py">list</a>(\*\*<a href="src/openai/types/beta/agents/automation_list_params.py">params</a>) -> <a href="./src/openai/types/beta/agents/automation.py">SyncCursorPage[Automation]</a></code>
- <code title="delete /agents/automations/{automation_id}">client.beta.agents.automations.<a href="./src/openai/resources/beta/agents/automations/automations.py">delete</a>(automation_id) -> <a href="./src/openai/types/beta/agents/automation_deleted.py">AutomationDeleted</a></code>
- <code title="post /agents/automations/{automation_id}/pause">client.beta.agents.automations.<a href="./src/openai/resources/beta/agents/automations/automations.py">pause</a>(automation_id) -> <a href="./src/openai/types/beta/agents/automation.py">Automation</a></code>
- <code title="post /agents/automations/{automation_id}/resume">client.beta.agents.automations.<a href="./src/openai/resources/beta/agents/automations/automations.py">resume</a>(automation_id) -> <a href="./src/openai/types/beta/agents/automation.py">Automation</a></code>

#### Invocations

Types:

```python
from openai.types.beta.agents.automations import AutomationInvocation, AutomationInvocationSource
```

Methods:

- <code title="post /agents/automations/{automation_id}/invocations">client.beta.agents.automations.invocations.<a href="./src/openai/resources/beta/agents/automations/invocations.py">create</a>(automation_id) -> <a href="./src/openai/types/beta/agents/automations/automation_invocation.py">AutomationInvocation</a></code>
- <code title="get /agents/automations/{automation_id}/invocations/{invocation_id}">client.beta.agents.automations.invocations.<a href="./src/openai/resources/beta/agents/automations/invocations.py">retrieve</a>(invocation_id, \*, automation_id) -> <a href="./src/openai/types/beta/agents/automations/automation_invocation.py">AutomationInvocation</a></code>
- <code title="get /agents/automations/{automation_id}/invocations">client.beta.agents.automations.invocations.<a href="./src/openai/resources/beta/agents/automations/invocations.py">list</a>(automation_id, \*\*<a href="src/openai/types/beta/agents/automations/invocation_list_params.py">params</a>) -> <a href="./src/openai/types/beta/agents/automations/automation_invocation.py">SyncCursorPage[AutomationInvocation]</a></code>

### Environments

Types:
Expand Down
Loading
Loading