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
16 changes: 16 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,22 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
# The authorable-surface deletion gate (#4650) below anchors on the
# MERGE BASE of HEAD with origin/main — that is the only anchor under
# which "a key this PR deleted" and "a key main gained since the fork
# point" are different facts. A shallow clone has no walkable
# ancestry, so `merge-base` fails and the gate falls back to
# origin/main's TIP, where those two facts collapse into one and the
# SECOND one is reported as the first: #6359 had PR #6356 (which
# touches no spec file at all) go red for "deleting"
# ui/BulkActionDef:requiredPermissions — a key main had just ADDED.
#
# Same line as the ESLint job above, opposite failure mode, and that
# is why it is spelled out here rather than cross-referenced: shallow
# degrades the slot-lookup ratchet to "not verified" (a false GREEN),
# and degrades this gate to a false RED on an innocent PR.
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v7
Expand Down
4 changes: 3 additions & 1 deletion packages/spec/scripts/build-schemas-check-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1517,7 +1517,9 @@ describe('build-schemas.ts — the drift notice names the direction it measured
// Truncation moves TWO things here, and the second was a surprise worth
// writing down: `merge-base HEAD origin/main` itself fails once the walk is
// cut, so `resolveSurfaceBase` falls back to origin/main's TIP as the
// baseline (it says so — "using origin/main tip … as the baseline anchor").
// baseline (it says so — "no merge base is walkable here, so this run
// anchors on the origin/main TIP …", the line #6359 reworded to name what
// that anchor then MISJUDGES).
// The pair being compared is therefore anchor-at-`tip` vs baseline-at-
// `mainTip`, not the fork point at all. And the ancestry between them is
// exactly what a grafted history cannot answer: `mainTip` is its own shallow
Expand Down
37 changes: 34 additions & 3 deletions packages/spec/scripts/build-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1509,8 +1509,17 @@ function resolveSurfaceBase(): SurfaceBaseResolution | null {
const git = gitInPackage;
const committed = readCommittedSurfaceBase();

// CI's typecheck job checks out shallow with no branch refs, so fetch the
// one ref this check needs (depth 1 — a single snapshot) before giving up.
// A checkout with no branch refs (any `fetch-depth: 1` job) cannot name
// origin/main at all, so fetch the one ref this check needs before giving up.
//
// This fetch is GUARDED by the probe above and that is load-bearing (#6359):
// where the ref already resolves — every full clone, and every CI job that
// checks out `fetch-depth: 0` — it does not run, so it cannot undo the depth
// its job asked for. Where it does run, it is `--depth=1` because the only
// thing it is trying to buy is the ability to NAME origin/main; deepening it
// here would silently make every shallow build pay for a full history it was
// configured not to want. The job that needs walkable ancestry declares that
// in its checkout step, which is where the cost is visible.
let tipProbe = git('rev-parse', '--verify', '--quiet', 'origin/main^{commit}');
if (tipProbe.status !== 0) {
git('fetch', '--quiet', '--depth=1', 'origin', '+refs/heads/main:refs/remotes/origin/main');
Expand All @@ -1526,7 +1535,29 @@ function resolveSurfaceBase(): SurfaceBaseResolution | null {
const mergeBase = git('merge-base', 'HEAD', tip);
const rev = mergeBase.status === 0 ? mergeBase.stdout.trim() : tip;
if (mergeBase.status !== 0) {
console.log(` (shallow history — using origin/main tip ${tip.slice(0, 12)} as the baseline anchor)`);
// That "…is the merge base anyway" holds only while the merge ref is
// fresh. It is generated when the PR opens or updates and goes STALE as
// main advances, so on a branch that forked earlier the tip anchor
// carries keys the fork point never had — and this gate reads every one
// of them as a line THIS commit deleted. The direction is worth spelling
// out because the verdict it produces ("deleted without proof") reads
// like a severe spec violation and costs far more to diagnose than to
// fix: #6359 was one CI job missing `fetch-depth: 0`, and the PR it
// reddened (#6356) had not touched packages/spec at all.
//
// Diagnostic only — the verdict below is unchanged. Making this route
// stop MISJUDGING rather than merely announcing itself is a separate
// decision with a much wider blast radius: this block is top-level, so
// every `gen:schema` runs it, which means every shallow job that builds
// @objectstack/spec (ci.yml `build-core`, docker-publish, release, …)
// takes this path whenever that build is a cache miss. Tracked in #6452.
console.log(
` (shallow history — no merge base is walkable here, so this run anchors on the\n` +
` origin/main TIP ${tip.slice(0, 12)} instead. ⚠️ Under a tip anchor a key that main ADDED\n` +
` after this branch forked is indistinguishable from a key this branch DELETED. If a\n` +
` deletion is reported below for a file you did not touch, check that first — and if\n` +
` this is CI, the job's checkout step needs \`fetch-depth: 0\` (#6359).)`,
);
}
const baseline = readSurfaceKeysAtRev(
git,
Expand Down
Loading