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
7 changes: 6 additions & 1 deletion .github/workflows/smart-ci-shadow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ jobs:
--base-ref "$BASE_REF" \
--merge-out artifacts/merge-sha.txt \
--tree-out artifacts/merge-tree-sha.txt \
--merge-base-out artifacts/merge-base-sha.txt \
--merge-base-tip-out artifacts/merge-base-tip-sha.txt \
--note-out artifacts/merge-ref-note.txt

- name: Plan
Expand All @@ -131,10 +133,13 @@ jobs:
if [ -s artifacts/merge-ref-note.txt ]; then
plan_args+=(--note "$(cat artifacts/merge-ref-note.txt)")
fi
if [ -s artifacts/merge-sha.txt ] && [ -s artifacts/merge-tree-sha.txt ]; then
if [ -s artifacts/merge-sha.txt ] && [ -s artifacts/merge-tree-sha.txt ] \
&& [ -s artifacts/merge-base-sha.txt ] && [ -s artifacts/merge-base-tip-sha.txt ]; then
plan_args+=(
--merge-sha "$(cat artifacts/merge-sha.txt)"
--merge-tree-sha "$(cat artifacts/merge-tree-sha.txt)"
--merge-base-sha "$(cat artifacts/merge-base-sha.txt)"
--merge-base-tip-sha "$(cat artifacts/merge-base-tip-sha.txt)"
)
fi
node scripts/ci/smart-ci/plan.mjs \
Expand Down
6 changes: 6 additions & 0 deletions ci/schemas/ci-plan.v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"headSha": { "type": ["string", "null"] },
"mergeSha": { "type": ["string", "null"] },
"mergeTreeSha": { "type": ["string", "null"], "description": "tree of refs/pull/N/merge at plan time — the binding the landed verifier matches against HEAD^{tree} (CI-03)" },
"mergeBaseSha": { "type": ["string", "null"], "pattern": "^[0-9a-f]{40}$", "description": "observed first parent of the same accepted refs/pull/N/merge observation as mergeSha and mergeTreeSha; null for non-PR and error plans" },
"mergeBaseTipSha": { "type": ["string", "null"], "pattern": "^[0-9a-f]{40}$", "description": "authenticated live base-ref tip when the accepted first parent moved after dispatch; null for an exact control-base match, non-PR plan, or error plan" },
"actor": {
"type": "object",
"additionalProperties": false,
Expand Down Expand Up @@ -113,5 +115,9 @@
"required": ["name", "message"],
"properties": { "name": { "type": "string" }, "message": { "type": "string" } }
}
},
"dependentRequired": {
"mergeBaseSha": ["mergeBaseTipSha"],
"mergeBaseTipSha": ["mergeBaseSha"]
}
}
8 changes: 7 additions & 1 deletion ci/schemas/ci-run.v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"additionalProperties": false,
"required": ["code", "detail"],
"properties": {
"code": { "enum": ["plan-missing", "plan-invalid", "planner-error", "plan-job-failed", "plan-job-cancelled", "head-sha-mismatch", "base-sha-mismatch", "policy-digest-mismatch", "trust-mismatch", "labels-mismatch", "mode-mismatch", "evidence-unavailable", "selected-evidence-missing", "selected-not-success", "evidence-sha-missing", "evidence-wrong-sha", "skipped-without-reason"] },
"code": { "enum": ["plan-missing", "plan-invalid", "planner-error", "plan-job-failed", "plan-job-cancelled", "head-sha-mismatch", "base-sha-mismatch", "merge-base-binding-mismatch", "policy-digest-mismatch", "trust-mismatch", "labels-mismatch", "mode-mismatch", "evidence-unavailable", "selected-evidence-missing", "selected-not-success", "evidence-sha-missing", "evidence-wrong-sha", "skipped-without-reason"] },
"detail": { "type": "string" }
}
}
Expand All @@ -32,6 +32,8 @@
"headSha": { "type": ["string", "null"] },
"mergeSha": { "type": ["string", "null"] },
"mergeTreeSha": { "type": ["string", "null"] },
"mergeBaseSha": { "type": ["string", "null"], "pattern": "^[0-9a-f]{40}$", "description": "observed merge-ref first parent copied from the plan; null when the plan is missing, legacy, non-PR, or errored" },
"mergeBaseTipSha": { "type": ["string", "null"], "pattern": "^[0-9a-f]{40}$", "description": "authenticated moved-base tip copied from the plan; null for an exact control-base match or unavailable binding" },
"risk": { "type": ["string", "null"] },
"trust": { "type": ["string", "null"] },
"escalated": { "type": ["boolean", "null"] },
Expand All @@ -54,5 +56,9 @@
}
},
"generatedAtUtc": { "type": "string", "format": "date-time" }
},
"dependentRequired": {
"mergeBaseSha": ["mergeBaseTipSha"],
"mergeBaseTipSha": ["mergeBaseSha"]
}
}
20 changes: 16 additions & 4 deletions docs/ci/SMART_CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,12 @@ event head (`CONTROL_HEAD`) to match the merge ref's second parent **exactly**
untrusted side and is never negotiable — while the first parent may be either the dispatch-time
control base (`CONTROL_BASE`) or the base branch's live tip on origin — the same branch whose
commit already supplies the control-plane tooling — which the resolver reads itself with
`--base-ref` before accepting. Nothing untrusted enters the binding. An accepted move is recorded as a `merge-ref-moved` planner note in the receipt.
`--base-ref` before accepting. Nothing untrusted enters the binding. The plan records the accepted
observation structurally: `mergeBaseSha` is the merge commit's observed first parent, and
`mergeBaseTipSha` is the authenticated live tip only for an accepted move (otherwise `null`). The
optional `merge-ref-moved` note is explanatory text and is never parsed as evidence.
Any other first parent, an unreadable base branch tip, or any head mismatch still fails closed with no
merge/tree outputs, which the planner turns into a `planner-error` plan. Before this rule
merge/tree/base outputs, which the planner turns into a `planner-error` plan. Before this rule
(CI-03 #2327) an ordinary base push produced a false red on a healthy PR; because an error plan pins
its trust class to `T3` by construction, the gate's re-derivation also emitted a misleading
`trust-mismatch` failure alongside it — that comparison is now reported as a note instead, and
Expand All @@ -134,7 +137,8 @@ decided on that observation evidence, not in advance (SC-4, no pre-ruling 2026-0
## 8. Receipts and reports

Every gate run writes `ci-run.json` (schema `ci/schemas/ci-run.v1.schema.json`): SHAs and merge
tree, policy digest, risk/trust, selected/skipped with reasons, per-job runner class / hosted flag /
tree, observed merge first parent and optional authenticated moved-base tip, policy digest,
risk/trust, selected/skipped with reasons, per-job runner class / hosted flag /
queue / setup / test / total seconds / allowance-minute estimate / tests run-failed-skipped / rerun /
cache hit / artifact bytes; summary critical path, aggregate runner seconds, hosted minutes and cost
estimate, self-hosted wall seconds, flake and duplicate-qualification flags. Names, ids, timestamps
Expand All @@ -143,11 +147,19 @@ P50/P95 critical path, minutes per merged PR, hosted cost, queue delay, selectio
per lane, flake rate, slow-test regressions, duplicate exact-SHA runs, cache utility and storage.
Provisional budgets: R0/R1 ≤5 min, R2 ≤10, R3 ≤20, main verifier ≤5 — a regression names the lane.

The version-1 executable reader accepts a legacy plan only when both `mergeBaseSha` and
`mergeBaseTipSha` are absent; it then uses `baseSha` as the historical parent fallback and explicitly
makes no claim that the new first-parent receipt was captured. A partial pair, malformed SHA, or
binding mismatch is invalid. New successful PR plans always carry both fields; non-PR and error plans
carry both as `null`. Because the old documentation schemas use `additionalProperties: false`, an old
schema cannot validate a new receipt containing these fields; consumers must use the updated schema.

The shadow recall report is read-only and uses GitHub REST plus `gh run download`; it does not need
GraphQL project quota. It counts unique merged PRs, while retaining every measurable head and rerun
attempt so an earlier failure cannot disappear behind a later green run. A PR is usable only when all
of its collected evidence is exact and its final head has a successful required run. The plan's fetched
merge commit must have the recorded base and head parents and merge tree, and the landed merge commit
merge commit must have the recorded observed first parent and head parent plus merge tree; the observed
first parent must be `baseSha` or the matching recorded `mergeBaseTipSha`. The landed merge commit
must match the final base, head and tree. Missing, duplicate, expired, stale or mismatched evidence
fails closed. A lane family is ready only after at least 20 usable merged PRs, an actual failure in that
family, 100% recall and no missed failure anywhere in the sample. Exit codes are `0` ready, `1`
Expand Down
Loading
Loading