Skip to content

Rebuild swift-ci.yml from the ground up and reduce the repository to what CI needs - #551

Merged
coenttb merged 29 commits into
mainfrom
feature/admin-cleanup
Aug 13, 2026
Merged

Rebuild swift-ci.yml from the ground up and reduce the repository to what CI needs#551
coenttb merged 29 commits into
mainfrom
feature/admin-cleanup

Conversation

@coenttb

@coenttb coenttb commented Aug 12, 2026

Copy link
Copy Markdown
Member

Rebuilds the universal reusable rather than continuing to refactor it, and reduces this repository to the files that serve it.

Why a rewrite

The step-by-step refactor kept failing for two reasons. swift-ci.yml had an undeclared dependency closure on its own repository's tree — thirteen dangling references — and the Tools/ extraction had moved the package name without the capability, leaving five CLI verbs the workflow called but nothing implemented. Neither is reachable by small steps.

Shape

swift-ci.yml: 3018 → ~710 lines, 17 inputs → 5, 4 secrets → 1, 16 jobs. Deleted: the three dispatch inputs, swift-version/macos-runner/enable-private-repos, the docs job and its five inputs, six advisory linter jobs, and every tag-ref branch. Repository overall: 865 files, −80,646 lines.

Two new composite actions carry what was duplicated. ci-subject replaces forty copies of the checkout + HEAD-proof + credential block across fourteen legs — fourteen places for a nine-line proof to drift, and one place for it to be silently dropped. central-style materialises the single .swift-format and .swiftlint.yml the fleet is measured against, displacing package-local copies at any depth.

Scheduling stays owned by institute-continuous-integration: plan emits the leg list and every job binds it, which is what lets ci-ok fail a gating leg that skipped without the plan having excluded it.

The binary channel

The plan job used to cold-build institute-continuous-integration through the Workspace coordinate — a ~30 minute SwiftPM build, a toolchain install, two checkouts and an apt provisioning set, on the critical path of the first job of every run of every package in the fleet. It appeared fast only while one actions/cache entry survived. Cache keys are exact by policy, so the first change to the sources or the provisioning set forced the cold path, and by then that path no longer linked.

It is now downloaded from a published rolling release, checksum-verified, and refused unless the manifest's recorded source revision equals .institute-ci-pin. ~30 minutes → 1.4 seconds, and the identity is stronger than a cache key rather than weaker. Producer: swift-institute/institute-continuous-integration#23.

Evidence

Four probes against this branch at full tier:

probe result
swift-effects success
swift-rfc-1123 success
swift-order-primitives every leg green except swift-format
swift-css-standard every leg green except SwiftLint

The two failures are genuine package content meeting the central rule set for the first time, not CI defects — ci-ok refuses with selectedLegNotSuccessful(job: "format", result: "failure"), which is correct. Ruled: land it, and each package fixes its own.

Notes for review

  • ci-ok keeps its name. ci / matrix / ci-ok is the only required status check on protected main; renaming it would silently un-gate the fleet. Verified against the ruleset, not assumed.
  • No fleet changes. Every caller keeps its uniform generated ci.yml and one hop. Merging this is the cutover, since all callers track @main.
  • Caller permission ceiling. A called workflow cannot exceed the caller's actions: read + contents: read; asking for more fails the run at startup before any job is scheduled.
  • install-system-deps, install-swift-sdk and configure-private-repos are restored to the tree. They were deleted while still in use, pinned to commits on main — which resolves, but left the workflow depending on files absent from the repository's own tree. All 25 in-repo action references now name one revision.
  • This repository carries no ci.yml caller, so this PR has no checks of its own. The probes above are the evidence.

coenttb added 24 commits August 12, 2026 11:19
TX-5 of the reduction census (#515). `Tools/institute-ci` and
`Tools/repository-policy` are gone; what they implemented now lives in
the packages that own it, and this repository consumes those packages the
same way every other consumer does.

**The binary channel.** `actions/institute-ci` and swift-ci.yml's two
inline bootstrap blocks provision
swift-institute/institute-continuous-integration at a pinned commit
instead of a directory in this checkout. The cache key was the git tree
OID of `Tools/institute-ci`; it is now the pinned commit, which is the
same kind of fact — exact content identity — and needs no working tree to
compute. Provenance verification is unchanged and still fail-closed.

**The policy data stays.** `Tools/repository-policy/Policy/` moves to
`policy/` at the root, beside `canon/`: it is fleet policy this
repository authors, not engine source. The engine now builds where it is
checked out, so the copy-the-engine-in-beside-the-data step is gone, and
`--surface-policy` is passed explicitly rather than falling through to a
package-relative default that only resolved because of that co-location.

**The shipped-bytes controls survive the move.** The suites that extract
swift-ci.yml's aggregator, subject resolver, linter installer, and the
system-dependency action's step travel with the reader they use, and
lint-validator-fixtures.yml names this checkout to them through
INSTITUTE_CONTROL_PLANE_ROOT. Without it they decline rather than report
green against a root they guessed.

**tools-tests.yml is retired.** It ran the two packages' suites through
`workspace package test` on a repository that no longer contains them;
each package now runs its own. No required status check named it.

The `sources-revision` pins here are the pre-port head of
institute-continuous-integration and MUST advance to the port's merge SHA
before this lands, or the provisioned binary will not carry the faces
these workflows call.
An audit of the previous commit found five defects it introduced. All are
fixed here.

**ci-ok built from one directory and copied from another.** The cold
bootstrap built `.institute-ci-sources` but copied the binary out of
`institute-receipt/.build` — the control-plane checkout, which carries no
Swift package at all any more. The `Tools/institute-ci` segment had been
stripped from the path instead of the whole prefix being replaced. Every
cold ci-ok run would have failed at `cp`.

**The pin had seven copies and only prose binding them.** It is now
`.institute-ci-pin` at the repository root, read by the composite action
(relative to its own path), by both swift-ci.yml bootstrap blocks, by
lint-validator-fixtures.yml, and by swift-docs.yml. Before this the cache
key `institute-ci-<pin>-…` could split silently on a partial advance: the
plan job minting a manifest at one revision while ci-ok verified at
another, each believing its provenance check passed. The tree OID it
replaced was a single runtime-computed authority; duplicating literals
gave that up, and this restores it.

**One fact carried two names.** The identity step emitted `sources-tree`
while carrying a commit SHA and passing it to `--sources-revision`; the
composite action already called it `sources-revision`. The two channels
that must agree on a cache key now spell it the same way.

**Both source checkouts ran unconditionally** while their only consumer is
cache-gated — a full clone added to every warm run of the universal
workflow, fleet-wide, for nothing. They now carry the same cache-miss gate
the composite action always had. The control-plane checkout in ci-ok stays,
because it is where that job reads the pin: routing it through
`needs.plan.outputs` would leave it empty exactly when plan failed and
ci-ok still has to report the plan-failure finding.

**A guard was credited to the wrong mechanism.** `ref:` comments claimed
the branch-pin guard rejects a non-SHA pin; it reads root `Package*.swift`
manifests and has never read a workflow ref. The refusal is the inline
40-hex check, and the comments now say so — a maintainer trimming
duplication could have deleted it believing something else covered it.

Also swept the stale Tools/ narration out of six workflows, replaced the
`reviewOnly` worked example that named a class of PR this repository can
no longer produce, and restored the precise test-target coordinate in the
schema-correspondence README.
`.swiftlint.yml` is a live fleet dependency, not repository furniture:
the universal's lint job checks this repository out at its own revision
and rewrites every consumer's `parent_config` to point at this exact
copy, so all 471 callers resolve their rule set from here. The rough
cleanup removed it, which would have failed that job fleet-wide at
"pinned Tier 1 .swiftlint.yml did not check out".

`.swift-format` is narrower and worth not confusing with it. The format
job reads each consumer's own file, discovered from the consumer's
checkout, so this one governs this repository's sources and stands as
the canonical copy the layer packages take. Restored on both counts.

The pin moves to the commit where the binary first implements the verbs
this workflow calls. Until #22 the extracted repository served three
gitignore verbs and none of `plan`, `aggregate`, `bootstrap-manifest`
or `bootstrap-verify`.
Forty duplicated step blocks across fourteen legs collapse into one
checkout-and-prove action. The proof each leg was carrying — that the
checkout is the revision the plan resolved — is the part worth having in
one place: fourteen copies is fourteen chances to drift and one chance
to be quietly dropped.

central-style displaces every package-local .swift-format and
.swiftlint.yml with the single central pair, at any depth. SwiftLint
prefers the nearest config to each file, so a leftover nested copy would
keep governing its own subtree — which is the non-uniformity this ends.
…rd it

3018 lines to 707, seventeen inputs to five, four secrets to one. The
file was 40% comment by line, and most of that was history: measurements
that justified a decision years of runs ago, arguments with positions
nobody holds any more, and the reasoning behind branches this commit
deletes. That belongs in Internal, where it already is. What stays is
the traps — the ones a future editor would otherwise walk back into.

The three dispatch inputs go. ci-dispatch and ci-sweep are retired, and
zero of the 473 live callers passed target-repo, job or ref; between
them they carried a subject-resolution ladder and a selector disjunct in
all eighteen job guards. swift-version, macos-runner and
enable-private-repos go too, on the same evidence: nobody passes them,
so their defaults were the contract.

Four secrets become one. 465 of 471 callers pass none at all, and the
credential action no-ops on empty input, so the PAT fallback and the
duplicate app-id spelling were configuring nothing anywhere. The six
that do pass one pass exactly the private key, which is the ruled
terminal profile the fleet had already converged on.

Docs, the six advisory linter jobs and every tag-ref branch are gone —
the first two by ruling, the third because the Institute develops
main-only and those branches could never fire.

Format and lint now displace any package-local configuration with the
central pair before measuring. Enforcement is uniform from this commit;
deleting the per-package copies afterwards is cleanup, not a cutover,
and cannot redden the fleet on its own.

Composite actions must carry full coordinates, not `./`: a relative
action path resolves against the workspace, which in a reusable holds
the consumer's package rather than this repository.
`--static-swift-stdlib` links Foundation statically, which pulls in
lib_CFURLSessionInterface.a and every libcurl symbol with it. The
bootstrap provisioned only uuid-dev, so the link failed with 40-odd
undefined references to curl_* and took the plan job down with it —
before the planner could classify anything.

Nothing in the binary opens a socket. The event diff is read by shelling
out to `gh`. The symbols still have to resolve for a static link, which
is why this is a provisioning defect rather than a dependency one.

Named in --provisioning as well as installed: the provisioning set is
part of the cache entry's identity, so omitting it there would let an
entry built without libcurl satisfy a key that claims to have it.
The action installed a Swift toolchain, checked out two repositories,
ran a SwiftPM build through the Workspace coordinate, and provisioned
apt packages — all on the critical path of the plan job, which is the
first job of every run of every package in the fleet. A cache entry made
that invisible until an exact key finally missed and the cold path was
found not to link.

Consume the published rolling release instead. Every run checks
SHA256SUMS and then refuses unless the manifest's recorded source
revision equals the fleet pin, so the tag is verified rather than
trusted, and a pin advance whose publish has not landed stops here
instead of running an older binary under a newer pin's name.
The consumer verifies the published manifest's recorded source revision
against this file and refuses on disagreement, so the pin now names the
revision the binary was actually built from rather than the last
revision anyone built from source.
…te CI pin

The action reads .institute-ci-pin relative to GITHUB_ACTION_PATH, so it
sees the pin as of its own pinned revision, not the workflow's. Advancing
the pin therefore has no effect until the action reference advances with
it — the previous references predated the advance and refused, correctly,
against the older pin.
The package-content predicate reads the event's changed files through
`gh`, which authenticates from the environment only; the step never set
GH_TOKEN, so the predicate refused on every pull request. Grant
pull-requests: read alongside it.

The 60 minute timeout was sized for the cold source build that no longer
exists. Ten is generous for a download and a classification.
A called workflow cannot request more than the caller grants, and the
generated caller grants actions: read and contents: read. Asking for
pull-requests: read failed the run at startup, before any job was
scheduled. The predicate needs no further grant: the visibility gate
means every repository reaching this step is public.
The plan step's summary ended with `[ -n "$D" ] && echo …`, which is the
last command in its group. On the ordinary case — nothing descheduled —
the test returns 1, the group returns 1, and set -e failed the step after
every output had already been written: a clean log above a bare exit 1.

An action manifest's description field is itself a template, so
central-style's spelling of the job-context expression in prose made the
whole action fail to load with "Unrecognized named-value: 'job'". Both
manifests now scan clean for templated descriptions.
swift-linter's publisher checksums MANIFEST.txt alongside the two
binaries, so `sha256sum --check` fails on a missing entry rather than
skipping it. The step downloaded only the binaries.
The advisory summary carried the same shape as the plan step: a test as
the last command of a group that is itself the script's last command, so
the ordinary empty case set the step's exit status to 1. Fixing one
instance without sweeping its class left the twin in place.
The sidebar lists jobs in definition order, so grouping needs both a
common prefix and the definitions themselves in that order. Legs are now
Linux, then Apple, then Windows, then the quality gates, and the prefix
survives the graph view's truncation — the distinguishing word comes
first instead of after 'Ubuntu (Swift'.

Four names still interpolated inputs.swift-version, an input the
reduction deleted, so they rendered as 'Ubuntu (Swift , release)'. The
version is owned by env and belongs in one place; a name that restates
it is a second declaration that can drift, which is exactly what
happened. Dropped rather than repaired.

ci-ok keeps its name: 'ci / matrix / ci-ok' is the required status check
named by the rulesets, and renaming it would silently un-gate the fleet.
Every job body is byte-identical; only names and order changed.
A skipped matrix job never expands, so a name referencing matrix.platform
has nothing to substitute and GitHub renders the template literally. The
leg is exhaustive-tier only and skipped in nearly every run, so that
unexpanded state is the one actually read.
install-system-deps, install-swift-sdk and configure-private-repos were
removed in the cleanup while swift-ci.yml and ci-subject kept using them,
pinned to commits on main. That resolves — a commit-pinned uses: is
immortal — but it left the workflow depending on files absent from the
repository's own tree, readable only by digging through history, and it
split the action pins across three unrelated revisions.
The workflow referenced its own repository's actions at three unrelated
commits, so 'the actions this workflow uses' was not a single reviewable
state. All 25 sites now name one revision.
The reduction retires Tools/ and .github/scripts/, and 64 of the 78 deleted
workflows go with them because their implementation was there. This one does
not: TX-1 of the reduction census already moved the transaction model into
swift-institute/institute-pull-request-transaction, and the workflow consumes
it from a pinned checkout at 5528f99b. It reaches nothing this branch removes.

It is also the sole distinct-reviewer route for a coenttb-authored pull
request, so deleting it would have removed the means of approving both this
change and every change after it.
@coenttb

coenttb commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Forward-only activation slice published:

Activation order is #10 and #5, then Institute CI #24, then this PR. No historical Python, fixture tree, support YAML, or workflow caller was restored.

# Conflicts:
#	.github/workflows/swift-ci.yml
#	.institute-ci-pin

@swift-institute-bot swift-institute-bot Bot left a comment

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.

Independent transaction review: exact-head guards satisfied via the control profile; approval is not merge authorization.

@coenttb
coenttb merged commit 7ebe7bc into main Aug 13, 2026
1 check passed
@coenttb
coenttb deleted the feature/admin-cleanup branch August 13, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant