Skip to content
Closed
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
167 changes: 162 additions & 5 deletions .github/actions/path-classifier/classify.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const vm = require('node:vm');

const OUTPUT_NAMES = {
'docs-only': 'is-docs-only',
Expand All @@ -25,6 +26,16 @@ const DEFAULT_CATEGORIES = {
'test-only': { paths: ['tests/**', '**/test_*.py', '**/*.test.js'], requireAll: true },
};

const STABLE_SYNC_BRANCHES = new Set([
'sync/workflows-candidate',
'sync/workflows-delivery',
]);

function isStableDeliveryPullRequest(githubContext) {
const branch = githubContext?.event?.pull_request?.head?.ref || '';
return githubContext?.event_name === 'pull_request' && STABLE_SYNC_BRANCHES.has(branch);
}

function normalizePath(value) {
return String(value || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
}
Expand Down Expand Up @@ -182,6 +193,122 @@ function parseGithubContext() {
}
}

function stableDeliverySealStatus(githubContext, { contract, now } = {}) {
const event = githubContext?.event || {};
const pullRequest = event.pull_request;
if (!isStableDeliveryPullRequest(githubContext)) {
return { required: false, valid: true, reason: '' };
}

const headRepository = pullRequest?.head?.repo?.full_name || '';
const baseRepository = pullRequest?.base?.repo?.full_name || '';
if (!headRepository || !baseRepository || headRepository !== baseRepository) {
return {
required: true,
valid: false,
reason: 'stable delivery must originate from the base repository',
};
}
if (!contract) {
return { required: true, valid: false, reason: 'delivery contract is unavailable' };
}

const record = contract.parseDeliveryRecord(pullRequest?.body || '');
const eligibility = contract.mergeEligibility(record, {
now: now || new Date().toISOString(),
repository: baseRepository,
requireSealed: true,
headSha: pullRequest?.head?.sha || '',
});
return {
required: true,
valid: Boolean(eligibility.eligible),
reason: eligibility.reason,
};
}

function compileDeliveryContract(source, filename) {
const module = { exports: {} };
const sandbox = { module, exports: module.exports };
vm.runInNewContext(String(source), sandbox, { filename });
return module.exports;
}

function readContractAtRef(ref, contractPath) {
return runGit(['show', `${ref}:${contractPath}`]);
}

function isAddOnlyContractDiff(diffText, contractPath) {
return String(diffText || '')
.split(/\r?\n/)
.some((line) => line === `A\t${contractPath}`);
}

function contractAddedBetweenRefs(baseSha, headSha, contractPath) {
const added = runGit([
'diff',
'--name-status',
'--diff-filter=A',
baseSha,
headSha,
'--',
contractPath,
]);
return isAddOnlyContractDiff(added, contractPath);
}

function loadDeliveryContract(
githubContext = {},
{
readTrustedContract = readContractAtRef,
readBootstrapContract = readContractAtRef,
isBootstrapAddition = contractAddedBetweenRefs,
} = {},
) {
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
const relativeContractPath = '.github/scripts/sync_pr_lease_contract.js';
const pullRequest = githubContext?.event?.pull_request;

if (isStableDeliveryPullRequest(githubContext)) {
const baseSha = pullRequest?.base?.sha || '';
if (!baseSha) {
return null;
}
try {
const source = readTrustedContract(baseSha, relativeContractPath);
return compileDeliveryContract(source, `${baseSha}:${relativeContractPath}`);
Comment thread
stranske marked this conversation as resolved.
} catch {
// A consumer's first stable-delivery rollout necessarily predates the
// lease contract on its base. Permit only that exact add-only bootstrap:
// same repository, exact observed head, and the contract path added (not
// modified or renamed) between base and head. Maint 71 remains the final
// boundary and independently requires the exact generated head to carry
// a valid GitHub-recognized signature before it can merge.
const headSha = pullRequest?.head?.sha || '';
const headRepository = pullRequest?.head?.repo?.full_name || '';
const baseRepository = pullRequest?.base?.repo?.full_name || '';
if (!headSha || !headRepository || headRepository !== baseRepository) {
return null;
}
try {
if (!isBootstrapAddition(baseSha, headSha, relativeContractPath)) {
return null;
}
const source = readBootstrapContract(headSha, relativeContractPath);
Comment on lines +293 to +297

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fetch the bootstrap head before reading its contract

For the first stable-delivery rollout, the base lacks this contract, but the pr-00-gate.yml detect job uses the default shallow actions/checkout and fetchBaseRef fetches only the base branch/base SHA. Consequently the exact pull_request.head.sha is not normally present locally, so both isBootstrapAddition(baseSha, headSha, ...) and readBootstrapContract(headSha, ...) fail and the classifier reports the contract as unavailable. Fresh evidence beyond the earlier bootstrap review is the actual checkout/fetch path: no command fetches the head SHA before this branch executes. Fetch the exact head SHA before checking the add-only diff.

Useful? React with 👍 / 👎.

return compileDeliveryContract(source, `${headSha}:${relativeContractPath}`);
} catch {
return null;
}
}
}

const contractPath = path.resolve(workspace, relativeContractPath);
if (!fs.existsSync(contractPath)) {
return null;
}
return require(contractPath);
}

function runGit(args) {
return execFileSync('git', args, {
cwd: process.env.GITHUB_WORKSPACE || process.cwd(),
Expand Down Expand Up @@ -224,7 +351,13 @@ function fetchBaseRef(baseRef, githubContext) {
}
}

function listChangedFiles({ baseRef, githubContext } = {}) {
function listChangedFiles({
baseRef,
githubContext,
baseAlreadyFetched = false,
fetchBase = fetchBaseRef,
diffGit = tryGit,
} = {}) {
const envFiles = process.env.PATH_CLASSIFIER_FILES_JSON;
if (envFiles) {
const parsed = JSON.parse(envFiles);
Expand All @@ -234,7 +367,9 @@ function listChangedFiles({ baseRef, githubContext } = {}) {
return parsed.map(normalizePath).filter(Boolean);
}

fetchBaseRef(baseRef, githubContext);
if (!baseAlreadyFetched) {
fetchBase(baseRef, githubContext);
}
const head = githubContext.sha || 'HEAD';
const ranges = [];
if (baseRef) {
Expand All @@ -248,7 +383,7 @@ function listChangedFiles({ baseRef, githubContext } = {}) {
}

for (const range of ranges) {
const output = tryGit(['diff', '--name-only', range]);
const output = diffGit(['diff', '--name-only', range]);
if (output) {
return output.split(/\r?\n/).map(normalizePath).filter(Boolean);
}
Expand Down Expand Up @@ -312,15 +447,32 @@ function writeOutputs(outputs) {

function main() {
const githubContext = parseGithubContext();
const baseRef = resolveBaseRef(process.env.INPUT_BASE_REF || '', githubContext);
// The stable-delivery contract is loaded from the exact trusted base SHA.
// Fetch it before evaluating a stable delivery seal, then reuse that fetch
// for changed-file classification. Ordinary PRs defer the same fetch until
// classification so every run fetches the base at most once.
const baseAlreadyFetched = isStableDeliveryPullRequest(githubContext);
if (baseAlreadyFetched) {
fetchBaseRef(baseRef, githubContext);
}
const seal = stableDeliverySealStatus(githubContext, {
contract: loadDeliveryContract(githubContext),
});
if (seal.required && !seal.valid) {
throw new Error(
`Mutable generated delivery is not mergeable: ${seal.reason}. ` +
'Maint 71 must seal this exact head after bounded reviewer settlement.',
);
}
const forceFull = String(process.env.INPUT_FORCE_FULL || '').toLowerCase() === 'true';
const configPath = process.env.INPUT_CONFIG_PATH || '.github/path-classification.yml';
const baseRef = resolveBaseRef(process.env.INPUT_BASE_REF || '', githubContext);
const config = loadConfig(configPath);
let files = [];
let conservativeFull = false;

try {
files = listChangedFiles({ baseRef, githubContext });
files = listChangedFiles({ baseRef, githubContext, baseAlreadyFetched });
} catch (error) {
conservativeFull = true;
console.warn(`::warning::Unable to list changed files; forcing full classification: ${error.message}`);
Expand All @@ -341,8 +493,13 @@ module.exports = {
OUTPUT_NAMES,
classifyFiles,
globToRegExp,
isAddOnlyContractDiff,
isStableDeliveryPullRequest,
listChangedFiles,
loadConfig,
loadDeliveryContract,
matchesAny,
normalizePath,
parseClassificationConfig,
stableDeliverySealStatus,
};
6 changes: 6 additions & 0 deletions .github/agents/registry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ default_agent: codex
# Shared keepalive marker prefix (agent-agnostic)
keepalive_marker_prefix: agent-keepalive

# Cross-agent credentials whose absence can form a concrete authority remedy.
# Provider credentials come from each routed agent's required_secrets list.
authority_shared_secrets:
- ACTIONS_BOT_PAT
- OPENAI_API_KEY

# Dedicated instrumentation-only contract for the Sol/Terra/Luna plumbing
# canary. The reusable workflow ref is replaced with the exact commit containing
# this runner before merge. Trial profiles are rejected by ordinary agent and
Expand Down
4 changes: 4 additions & 0 deletions .github/scripts/error_classifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ function classifyByMessage(message) {
if (matchesPattern(message, TRANSIENT_PATTERNS)) {
return ERROR_CATEGORIES.transient;
}
// normaliseMessage lowercases classifier input before this branch.
if (/\bmissing\s+[a-z][a-z0-9_.-]*\s+auth\s*:\s*set\s+(?:the\s+)?[a-z][a-z0-9_]{2,}\b/.test(message)) {
return ERROR_CATEGORIES.auth;
}
if (matchesPattern(message, AUTH_PATTERNS)) {
return ERROR_CATEGORIES.auth;
}
Expand Down
86 changes: 86 additions & 0 deletions .github/scripts/gate_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import json
import os
import re
import sys
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path


Expand All @@ -22,6 +24,9 @@ class SummaryContext:
python_required: bool = True
docs_guard_result: str = "success"
test_quality_result: str = "skipped"
delivery_seal_required: bool = False
delivery_seal_valid: bool = True
delivery_seal_reason: str = ""


@dataclass(slots=True)
Expand All @@ -43,6 +48,65 @@ class SummaryResult:
"pending": 5,
}

STABLE_SYNC_BRANCHES = {"sync/workflows-candidate", "sync/workflows-delivery"}
DELIVERY_RECORD_PATTERN = re.compile(r"<!--\s*sync-pr-delivery-record:v1\s+([\s\S]*?)\s*-->")


def _delivery_seal_from_event(event_path: Path | None) -> tuple[bool, bool, str]:
"""Return whether a stable generated delivery is sealed to its exact head."""
if event_path is None or not event_path.is_file():
return False, True, ""
try:
payload = json.loads(event_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return False, True, ""
pull_request = payload.get("pull_request")
if not isinstance(pull_request, Mapping):
return False, True, ""
head = pull_request.get("head")
branch = str(head.get("ref") or "") if isinstance(head, Mapping) else ""
if branch not in STABLE_SYNC_BRANCHES:
return False, True, ""
head_repository = (
str(head.get("repo", {}).get("full_name") or "")
if isinstance(head, Mapping) and isinstance(head.get("repo"), Mapping)
else ""
)
base_repository = str(pull_request.get("base", {}).get("repo", {}).get("full_name") or "")
if not head_repository or not base_repository or head_repository != base_repository:
return True, False, "stable delivery must originate from the base repository"
head_sha = str(head.get("sha") or "") if isinstance(head, Mapping) else ""
body = str(pull_request.get("body") or "")
match = DELIVERY_RECORD_PATTERN.search(body)
if not match:
return True, False, "missing delivery record"
try:
record = json.loads(match.group(1))
except json.JSONDecodeError:
return True, False, "invalid delivery record"
if record.get("schema") != "sync-pr-delivery-record/v1":
return True, False, "invalid delivery schema"
if record.get("terminal_disposition"):
return True, False, "terminal delivery record"
if record.get("delivery_state") != "sealed":
return True, False, f"delivery state is {record.get('delivery_state') or 'missing'}"
if not head_sha or record.get("sealed_head_sha") != head_sha:
return True, False, "sealed head does not match the PR head"
repository = base_repository
if record.get("repository") != repository:
return True, False, "delivery repository does not match the PR"
try:
lease_expires_at = datetime.fromisoformat(
str(record.get("lease_expires_at") or "").replace("Z", "+00:00")
)
except ValueError:
return True, False, "delivery lease is invalid"
if lease_expires_at.tzinfo is None:
return True, False, "delivery lease is invalid"
if lease_expires_at <= datetime.now(UTC):
return True, False, "delivery lease expired"
return True, True, "exact head sealed"


def _normalize(value: str | None, default: str = "unknown") -> str:
if value is None:
Expand Down Expand Up @@ -294,6 +358,18 @@ def _active_lines(
def summarize(context: SummaryContext) -> SummaryResult:
docs_guard_result = _normalize(context.docs_guard_result or "success")

if context.delivery_seal_required and not context.delivery_seal_valid:
reason = context.delivery_seal_reason or "exact-head seal missing"
return SummaryResult(
lines=[
"### Gate status",
f"Generated delivery hold: {_emoji('failure')} {reason}.",
"Maint 71 must complete bounded review settlement and seal this exact head.",
],
state="failure",
description=f"Generated delivery is not sealed: {reason}.",
)

if context.doc_only or not context.run_core:
lines = _doc_only_lines(context.reason, docs_guard_result)
description = (
Expand Down Expand Up @@ -431,6 +507,13 @@ def build_context() -> SummaryContext:
artifacts_root = Path(os.environ.get("GATE_ARTIFACTS_ROOT", "gate_artifacts"))
summary_path = _resolve_path("GITHUB_STEP_SUMMARY")
output_path = _resolve_path("GITHUB_OUTPUT")
delivery_seal_required, delivery_seal_valid, delivery_seal_reason = _delivery_seal_from_event(
_resolve_path("GITHUB_EVENT_PATH")
)
if _normalize(os.environ.get("DELIVERY_SEAL_RESULT"), "success") == "failure":
delivery_seal_required = True
delivery_seal_valid = False
delivery_seal_reason = delivery_seal_reason or "generated delivery seal job failed"

return SummaryContext(
doc_only=doc_only,
Expand All @@ -445,6 +528,9 @@ def build_context() -> SummaryContext:
summary_path=summary_path,
output_path=output_path,
python_required=python_required,
delivery_seal_required=delivery_seal_required,
delivery_seal_valid=delivery_seal_valid,
delivery_seal_reason=delivery_seal_reason,
)


Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/github-api-with-retry.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ async function withRetry(fn, options = {}) {
? 'rate limit'
: 'transient error';

console.log(
console.error(
`${retryReason} (attempt ${attempt + 1}/${maxRetries + 1}). ` +
`Retrying in ${Math.round(actualDelay / 1000)}s...`
);
Expand Down
Loading
Loading