Skip to content

Extract MistKitConfiguration; converge all three examples on typed config keys - #455

Merged
leogdion merged 25 commits into
v1.0.0-beta.5from
407-mistkitconfiguration
Aug 31, 2026
Merged

Extract MistKitConfiguration; converge all three examples on typed config keys#455
leogdion merged 25 commits into
v1.0.0-beta.5from
407-mistkitconfiguration

Conversation

@leogdion

Copy link
Copy Markdown
Member

Closes #407 when complete. Draft — Parts 0–2 are done, Parts 3–5 are in progress.

Why this shape

#407 proposed extracting five things into a new package. Verifying each against the code showed most no longer held — Environment parsing, the credential types and the service factory are all already public MistKit API, and the ConfigKeyKitConfiguration package the issue’s diagram assumed never existed (that bridge shipped in ConfigKeyKit’s dependency-free core as ConfigValueReading). So the order is inverted: converge the examples first, then extract what is genuinely identical. Extracting first would have packaged code that convergence deletes.

What is done

PR 1 work (3 commits, previously unpushed) — BushelCloud and CelestraCloud converged onto MistKit’s Credentials API and ConfigKeyKit’s ConfigValueReading, deleting ~180 lines of character-identical glue.

Part 0 — MistDemo onto typed ConfigKeys (5338914)
MistDemo had zero typed keys (Bushel 32, Celestra 12) and read config through an untyped façade at ~160 call sites. The five CloudKit credential keys now use the names the package owns; every other key keeps its base and gains envPrefix: "CLOUDKIT", reproducing the blanket prefixKeys(with:) the provider stack applied to the whole key space — so no other flag or variable moves.

Part 1–2 — the package (8603e60) at Packages/MistKitConfiguration/, scaffolded from the ConfigKeyKit repo template.

ConfigKeyKit fix + pin (cc8743f) — see below.

Bugs this fixes

  1. CLOUDKIT_CONTAINER_ID never worked. MistDemo-Integration.yml sets it on all four jobs and docs/cloudkit-guide/ documents it, but the code read CLOUDKIT_CONTAINER_IDENTIFIER. Every integration run silently fell back to the built-in default — masked only because that default equals the secret’s value. Point it at a different container and it was ignored.
  2. Boolean CLI flags resolved wrongly, in ConfigKeyKit: --flag read as its default and --flag false read as true, while an unrecognized env value collapsed to false instead of being ignored. Fixed in Resolve booleans via a bool primitive, not string parsing ConfigKeyKit#8 and pinned here by revision. BushelCloud alone has ~14 affected ConfigKey<Bool> values.
  3. A test parameter that did nothingMistDemoConfig+Testing seeded private.key.file; production reads private.key.path, so privateKeyFile: never reached the parsing path.
  4. Deletes a 30-constant dead ConfigKeys enum and Defaults.database, which said private while the runtime default was public and was never read.

Design notes worth review

  • Errors are identifiable, not prose. The package throws Equatable enums and deliberately does not conform to LocalizedError: all three consumers already own an error type with its own wording, remediation advice and key names. Errors name a CloudKitConfigurationField rather than a key string, because the same field is spelled differently per app. This retires CredentialValidationError, whose reason/suggestion strings flattened four distinct key-ID failures into one unswitchable case and hardcoded CLOUDKIT_KEY_ID into a package that cannot know it.
  • ValidatedCloudKitConfiguration’s initializer is throwing and runs both validators, so no value of that type can exist whose credentials skipped format checking.
  • secretCommandLineFlags is derived from each key’s isSecret, structurally preventing the drift that let a private key passed by flag be logged unredacted.
  • output.format collapses from 25 sites with three different defaults to one key defaulting to table.

The MistKit dependency is a path:, deliberately

A tagged url: breaks the monorepo build. A path: package takes its identity from the directory name, so pairing it with a sibling depending on MistKit by url: makes SwiftPM resolve two packages and fail with multiple similar targets MistKit, MistKitOpenAPI. swift package resolve still succeeds — only a build catches it. Every package in this monorepo must reach MistKit the same way. That line is a monorepo-local overlay; the standalone repo carries the url: form, swapped by setup-mistkit in CI.

Verification

MistDemo 1010 tests / 299 suites (1002 baseline + 8 new)
MistKitConfiguration 35 tests / 6 suites, zero lint warnings
ConfigKeyKit fix 63 tests, verified end-to-end against a real ConfigReader
MistKit core untouched — git diff origin/v1.0.0-beta.5 -- Sources/ Package.swift is empty

SwiftLint is at parity for MistDemo: two pre-existing errors in files this PR never touches, two warnings resolved.

Remaining

  • Part 3 — rewire CelestraCloud, then BushelCloud onto the package.
  • Part 4 — MistDemo adopts it (key group included, the payoff of Part 0).
  • Part 5git subrepo wiring, setup-mistkitconfiguration, monorepo CI lane, close out Create MistKitConfiguration package for shared CloudKit config glue #407.
  • Swap the ConfigKeyKit revision pin for a tagged release once ConfigKeyKit#8 merges — dependency-policy.yml gates exactly this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BB4QwYjmEPMC2Fo5HW4cKd

leogdion and others added 7 commits August 31, 2026 10:04
… CLI flags

Delete the hand-rolled `read(_:)` glue duplicated in BushelCloud and
CelestraCloud in favor of ConfigKeyKit 1.0.0-beta.2's `ConfigValueReading`,
whose protocol extension supplies every overload both files declared. Four of
them were character-for-character identical between the two examples.

Standardize Bushel's ConfigKey bases on dash-case, matching Celestra.
`CLIKeyEncoder` joins a key's components verbatim, so snake_case bases
generated `--cloudkit-key_id` while Bushel's own error messages,
`secretsSpecifier` and docs all advertise `--cloudkit-key-id`. The documented
flags therefore never resolved, and secret redaction never matched, so a
private key passed by flag was not marked secret. ENV names are unchanged
(the environment provider normalizes `-` and `.` to `_`), so deployments and
CI are unaffected.

Fail closed on an unparseable CLOUDKIT_ENVIRONMENT in Celestra, which
previously degraded silently to .development, matching Bushel and MistDemo.

Expose Bushel's loader test seam unconditionally and add the same to
Celestra, whose loader was previously untestable without mutating process
environment.

Rebuild the Bushel test double on the real providers instead of
InMemoryProvider. The double matched keys literally and served only the
stored case, so it diverged from production on key normalization and numeric
coercion. It also stored bare flags as `.string("true")`, masking that
valueless flags never resolved through the real CLI provider — a gap that
predates this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Converge BushelCloud and CelestraCloud on one CloudKit config shape, so the
shared surface can be extracted into a package. Breaking for both subrepos.

Replace Bushel's `CloudKitAuthMethod` with MistKit's `PrivateKeyMaterial`,
which it re-declared. `ValidatedCloudKitConfiguration` now carries a single
`privateKey: PrivateKeyMaterial` instead of a `privateKeyPath` plus optional
`privateKey` plus an empty-string sentinel, collapsing the two-branch auth
resolution duplicated across five Bushel command sites.

Migrate both examples off the legacy `ServerToServerAuthManager` path onto
`CloudKitService(containerIdentifier:credentials:environment:)`. MistKit
defers reading a `.file(path:)` key until the credentials are consumed, so
construction no longer does file IO. Celestra gains the inline-PEM path it
lacked, which is what CI needs to avoid writing a temporary key file.

Share PEMValidator and KeyIDValidator with Celestra, which validated neither.
They throw a new app-neutral `CredentialValidationError`, so both copies are
byte-identical. A Server-to-Server key ID is 64 hex characters; correct
.env.example, SECRETS_SETUP.md and CLOUDKIT_SYNC_SETUP.md, which documented 32.

Unify the configuration error on one `ConfigurationError` conforming to
LocalizedError. Bushel's was a bare Error whose message never reached users.

Delete `CelestraConfig`, whose sole factory is now
`ValidatedCloudKitConfiguration.makeCloudKitService()`.

BushelCloudKitService.swift drops from 314 to 268 lines, clearing a
file_length lint error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Captures PR 1 (unify, done and unpushed), the push/merge commands, and what
PR 2 (extract into brightdigit/MistKitConfiguration) still needs — including
the repo creation and subrepo pushes left as user actions.

Records why the issue changed shape: most of #407's premises were stale (the
ConfigKeyKit#1 blocker resolved in-core, and the Environment/credential/factory
targets already exist in MistKit), so the work was reordered to converge the
examples first and extract second.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings MistDemo onto the typed-key pattern PR 1 gave BushelCloud and
CelestraCloud, so all three examples share one key convention before the
shared surface is extracted into MistKitConfiguration.

The five CloudKit credential keys adopt the names the package will own
(`cloudkit.container-id`, `cloudkit.key-id`, `cloudkit.private-key`,
`cloudkit.private-key-path`, `cloudkit.environment`). Every other key keeps
its historical base and gains `envPrefix: "CLOUDKIT"`, faithfully
reproducing the blanket `prefixKeys(with: "cloudkit")` the provider stack
applied to the whole key space — so no other flag or variable moves.

Environment variables: four unchanged, one repaired, none broken.
`container.identifier` resolved to CLOUDKIT_CONTAINER_IDENTIFIER, which
nothing ever set; MistDemo-Integration.yml supplies CLOUDKIT_CONTAINER_ID on
all four jobs and docs/cloudkit-guide documents that name, so the value CI
passed was silently ignored and every run fell back to the built-in default.
It only went unnoticed because that default equals the secret's value.

CLI: five credential flags gain a `cloudkit-` prefix. CI passes credentials
by environment, not flags, so no workflow changes are needed.

Fixes bare boolean flags. `optionalBool` probed `string(forKey:)` first, and
swift-configuration surfaces a valueless flag only through `bool(forKey:)`,
so `--zone-wide`, `--numbers-as-strings` and `--fetch-root-record` only
worked as `--flag true`. Measured: string(forKey:"verbose") is nil while
bool(forKey:"verbose") is true. ConfigKeyKit's own resolvedBool has the same
flaw, so MistDemoConfiguration keeps a Bool path over `bool(forKey:)` — that
both fixes the three broken sites and avoids regressing the twelve
`bool(forKey:default:)` flags that already worked.

output.format collapses from 25 sites with three different defaults to one
key defaulting to `table`; `--output-format json` opts back in. No CI step
parses command output, and all four formats have renderers.

Also: deletes the 30-constant ConfigKeys enum (now fully dead) and
Defaults.database, which said "private" while the runtime default was
"public" and was never read; rebuilds the test doubles on the real
environment provider keyed by typed keys, which surfaced that
MistDemoConfig+Testing seeded `private.key.file` — a key production never
read, so `privateKeyFile:` silently did nothing; removes README claims about
a `--config-file` that no provider implements.

1010 tests / 299 suites pass (1002 baseline + 8 new pinning the env-var
names, dash-case bases, secret flags and bare-flag resolution). SwiftLint is
at parity: two pre-existing errors in untouched files, two warnings resolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BB4QwYjmEPMC2Fo5HW4cKd
Extracts the CloudKit credential configuration surface that BushelCloud and
CelestraCloud converged on in PR 1 into its own package, scaffolded from the
ConfigKeyKit repo template. Lives at Packages/MistKitConfiguration so it can
be co-developed with MistKit and pushed to brightdigit/MistKitConfiguration
as a subrepo. MistKit's own Package.swift is untouched — the dependency arrow
points one way, which is why this is a separate repo rather than a product.

Errors are generic and identifiable, carrying no prose. CloudKitConfigurationError,
KeyIDValidationFailure and PEMValidationFailure are Equatable enums that
deliberately do not conform to LocalizedError: all three consumers already own
an error type with its own wording, remediation advice and key names, so
package-authored text would contradict every one of them. Errors name a
CloudKitConfigurationField rather than a key string, because the same field is
spelled differently per application; CloudKitConfigurationKeys.subscript(_:)
maps a field back to that application's own key. This retires
CredentialValidationError, whose two cases each carried reason/suggestion
strings that flattened four distinct key-ID failures into one unswitchable
case and hardcoded CLOUDKIT_KEY_ID into a package that cannot know it.

ValidatedCloudKitConfiguration's memberwise initializer is throwing and runs
both validators, so no value of that type can exist whose credentials skipped
format checking — the property that lets callers delete hand-rolled checks.
validated() checks presence before format and prefers an inline PEM over a
path. Reading stays non-throwing so it composes into any application's loader.

secretCommandLineFlags is derived from each key's isSecret rather than
hand-listed, structurally preventing the drift that previously let a private
key passed by flag be logged unredacted; a regression test asserts it.

Package.swift uses .package(name: "MistKit", path: "../..") rather than a
tagged url:. Verified empirically: a path package takes its identity from the
directory name, so pairing it with a sibling depending on MistKit by url:
resolves two distinct packages and fails with "multiple similar targets
'MistKit', 'MistKitOpenAPI'". swift package resolve still succeeds, so only a
build catches it. That line is a monorepo-local overlay; the standalone repo
carries the url: form, swapped by setup-mistkit in CI.

CI follows CelestraCloud rather than ConfigKeyKit because tools-version 6.4
has no Linux or Windows release toolchain (verified: Docker Hub lists 88
swift:6.2 tags, 83 swift:6.3, zero swift:6.4). Ubuntu runs the single
nightly-6.4.x entry, Windows is commented out, Android is omitted, macOS runs
on xcode-27, and dependency-policy.yml gates main on tagged dependencies.

35 tests in 6 suites pass; swift-format and SwiftLint are clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BB4QwYjmEPMC2Fo5HW4cKd
ConfigKeyKit resolved booleans through `string(forKey:)`, which cannot see a
valueless command-line flag, and coerced unrecognized environment values to
false instead of ignoring them. Fixed in brightdigit/ConfigKeyKit#8; pin by
revision until a release carrying it is tagged.

Verified the pin coexists with the `from: "1.0.0-beta.2"` requirement the three
examples declare: SwiftPM resolves the revision for the whole graph, so once
they depend on MistKitConfiguration they inherit the fix without editing their
own ConfigKeyKit line.

Note the revision requirement is deliberately incompatible with
dependency-policy.yml, which rejects non-tagged dependencies on PRs to main —
that gate is what will force the swap to a tagged release before merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BB4QwYjmEPMC2Fo5HW4cKd
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 056e01fb-cefb-4042-901e-c3fb92cee7ad

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 17.88079% with 124 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.76%. Comparing base (8896986) to head (5d256e9).
⚠️ Report is 5 commits behind head on v1.0.0-beta.5.

Files with missing lines Patch % Lines
...s/MistKitConfiguration/CloudKitConfiguration.swift 0.00% 30 Missing ⚠️
...Configuration/ValidatedCloudKitConfiguration.swift 0.00% 22 Missing ⚠️
...stKitConfiguration/CloudKitConfigurationKeys.swift 60.97% 16 Missing ⚠️
...es/MistKitConfiguration/ConfigurationSources.swift 0.00% 15 Missing ⚠️
...on/Sources/MistKitConfiguration/PEMValidator.swift 0.00% 12 Missing ⚠️
...tKitConfiguration/CloudKitConfigurationError.swift 0.00% 10 Missing ⚠️
.../Sources/MistKitConfiguration/KeyIDValidator.swift 0.00% 10 Missing ⚠️
...KitConfiguration/ConfigValueReading+CloudKit.swift 0.00% 8 Missing ⚠️
...rces/MistKitConfiguration/ConfigurationError.swift 0.00% 1 Missing ⚠️
Additional details and impacted files
@@                Coverage Diff                @@
##           v1.0.0-beta.5     #455      +/-   ##
=================================================
- Coverage          81.83%   79.76%   -2.07%     
=================================================
  Files                197      207      +10     
  Lines               4778     4942     +164     
=================================================
+ Hits                3910     3942      +32     
- Misses               868     1000     +132     
Flag Coverage Δ
mistdemo-spm-macos 11.41% <17.88%> (+0.17%) ⬆️
mistdemo-swift-6.2-jammy ?
mistdemo-swift-6.2-noble ?
mistdemo-swift-6.3-jammy ?
mistdemo-swift-6.3-noble ?
mistdemo-swift-6.4-jammy 11.41% <17.88%> (+0.17%) ⬆️
mistdemo-swift-6.4-noble 11.41% <17.88%> (+0.17%) ⬆️
spm 80.51% <ø> (+0.08%) ⬆️
swift-6.1-jammy 80.36% <ø> (-0.11%) ⬇️
swift-6.1-noble 80.61% <ø> (-0.04%) ⬇️
swift-6.2-jammy 80.63% <ø> (+0.04%) ⬆️
swift-6.2-noble 80.66% <ø> (+0.25%) ⬆️
swift-6.3-jammy 80.55% <ø> (-0.09%) ⬇️
swift-6.3-noble 80.55% <ø> (+0.04%) ⬆️
swift-6.4-jammy 80.59% <ø> (+0.02%) ⬆️
swift-6.4-noble 80.43% <ø> (-0.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

leogdion and others added 6 commits August 31, 2026 13:23
…tion.git --branch=initial-extraction Packages/MistKitConfiguration

subrepo:
  subdir:   "Packages/MistKitConfiguration"
  merged:   "cc8743f"
upstream:
  origin:   "git@github.com:brightdigit/MistKitConfiguration.git"
  branch:   "initial-extraction"
  commit:   "none"
git-subrepo:
  version:  "0.4.9"
  origin:   "https://github.com/Homebrew/brew"
  commit:   "0942cac2ed"
Points Packages/MistKitConfiguration at brightdigit/MistKitConfiguration
(branch initial-extraction), where the package now lives as PR #1.

The repository was seeded by hand rather than by `git subrepo push`: it was
empty, and GitHub cannot open a PR between unrelated histories, so `main`
needed a LICENSE-only initial commit first to give initial-extraction a merge
base. `.gitrepo` therefore records an empty `commit =` — the first
`git subrepo push` will believe nothing has been pushed.

That matters, because the two copies of Package.swift deliberately differ on
one line: the monorepo needs `path: "../.."` (a path package takes its identity
from the directory name, so a transitive `url:` MistKit resolves as a second
package and fails the build), while the standalone repo needs the tagged `url:`
or a tag of this package is unusable downstream. `git subrepo push` copies the
subdir verbatim and would clobber that line, so both Package.swift and CLAUDE.md
now say so at the site, and a memory records it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BB4QwYjmEPMC2Fo5HW4cKd
The previous handoff was written before any of this work and is stale in every
section: it says nothing is pushed, that the MistKitConfiguration repo does not
exist, that PR 2 has not started, and that Swift 6.4 needs a snapshot toolchain.

Records what is actually true now: three open PRs and the merge order they
force, the nine commits on the branch, Parts 0-2 as landed, and the two hazards
that fail silently — `git subrepo push` overwriting the standalone Package.swift
overlay, and a `path:` MistKit coexisting with a `url:` one, where
`swift package resolve` succeeds and only a build catches it.

Also carries forward the boolean correction, since it inverts my earlier
reasoning: typed keys do not fix bare flags, and migrating MistDemo naively onto
ConfigKeyKit's `read(_:)` would have regressed twelve working flags rather than
fixing three. `MistDemoConfiguration.resolveBool` exists for that reason and
should not be "simplified" away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BB4QwYjmEPMC2Fo5HW4cKd
…istDemo

Rewire the three examples onto the shared package (Parts 3–5 of #407), pin
ConfigKeyKit to main until it is tagged, and add MistKitConfiguration CI wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror the standalone package gate so draft integration can pin
ConfigKeyKit to branch main until a release tag exists.

Co-authored-by: Cursor <cursoragent@cursor.com>
PR #455 targets v1.0.0-beta.5, so branches: [main] never scheduled
the MistDemo/Bushel/Celestra/MistKitConfiguration matrix.

Co-authored-by: Cursor <cursoragent@cursor.com>
@leogdion

Copy link
Copy Markdown
Member Author

Temporarily closing/reopening to re-trigger Actions after synchronize events stopped scheduling PR workflows.

@leogdion leogdion closed this Aug 31, 2026
@leogdion leogdion reopened this Aug 31, 2026
@leogdion
leogdion marked this pull request as ready for review August 31, 2026 18:36
@leogdion
leogdion marked this pull request as draft August 31, 2026 18:36
@leogdion
leogdion marked this pull request as ready for review August 31, 2026 18:37
leogdion and others added 2 commits August 31, 2026 14:37
Synchronize events after f671c91 stopped scheduling MistKit/MistDemo
workflow runs; Package.swift is in both path filters.

Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve Package.resolved conflicts by regenerating against the
MistKitConfiguration path dependency. Unblocks PR Actions that were
stuck while the PR was CONFLICTING.

Co-authored-by: Cursor <cursoragent@cursor.com>
@leogdion
leogdion marked this pull request as draft August 31, 2026 18:39
leogdion and others added 5 commits August 31, 2026 14:42
That package gitignores the lockfile (standalone CI already passes
skip-package-resolved); without the flag swift-build --force-resolved-versions
fails immediately in the Examples matrix.

Co-authored-by: Cursor <cursoragent@cursor.com>
MistDemo now path-depends on MistKitConfiguration (tools 6.4), so 6.2/6.3,
Xcode 26.6, Windows, and Android lanes cannot parse the graph. Align MistDemo
with MistKitConfiguration's nightly/Xcode 27 matrix, and run MKC lint inside
the 6.4 nightly container so `swift build` in lint.sh succeeds.

Co-authored-by: Cursor <cursoragent@cursor.com>
ConfigKeyKit 1.0.0-beta.3 ships PR #8 (boolean resolution via a bool
primitive), the fix MistKitConfiguration was tracking off `branch: "main"`.
Swap the temporary branch pin for the released tag.

The pinned revision is unchanged (3c8ae38), so this is a provenance change
only — no behavior difference. `dependency-policy.yml` rejects branch and
revision requirements on non-draft PRs to `main`, so this is a prerequisite
for MistKitConfiguration#1 leaving draft.

35 tests pass on Swift 6.4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`.gitrepo` carried an empty `commit =` because the standalone repo was seeded
by hand rather than by `git subrepo push` (GitHub cannot open a PR between
unrelated histories, so `main` needed a LICENSE-only base commit first).

Record the commit the ConfigKeyKit bump landed on so future subrepo operations
have a diff base.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MistDemo reached ConfigKeyKit transitively through MistKitConfiguration, which
pinned `branch: "main"`, so the lockfile recorded a floating branch. With
MistKitConfiguration on `from: "1.0.0-beta.3"` the same revision (3c8ae38) now
locks as a tagged version instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
leogdion and others added 4 commits August 31, 2026 15:59
Two facts learned while unblocking the MistKitConfiguration release:

- A workflow job gated on `draft == false` never runs on the draft → ready
  transition unless `types:` lists `ready_for_review`. It reports `skipped`,
  not `failure`, so the gate silently fails open on exactly the PRs it guards.
- `git subrepo push` for Packages/MistKitConfiguration would carry the monorepo
  `path:` MistKit overlay into the standalone repo; push via a clone instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tagging MistKitConfiguration does not mean the Examples switch to `from:`.
They dogfood unreleased MistKit, so every in-monorepo package must be reached
the same way — `path:` locally, rewritten to a branch-HEAD `revision:` pin in
CI by `setup-mistkitconfiguration`, which takes both branch inputs precisely
because a `path:` MistKit and a `url:` MistKit cannot coexist.

Verified both failure modes: pointing only MistKitConfiguration at its tag
reproduces the duplicate-target error, and additionally moving MistKit to
1.0.0-beta.4 makes MistDemo fail to compile on `ZoneType` / `ZoneInfo.deleted`
(#444, branch-only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ndoff doc

`.gitrepo` pointed at `initial-extraction`, which was squash-merged as 6df3ad4 —
its commits are not on `main`, so a subrepo pull would diff against history that
no longer exists. Repoint at `mistkit-beta.5` (a70afee), the integration branch
pinning MistKit's unreleased `v1.0.0-beta.5` so MistKitConfiguration can be
exercised against #444 before the tag exists. That branch must never merge to
`main` or be tagged; `main` stays tag-only for downstream consumers.

Delete `.claude/HANDOFF-407.md`: its merge gate is resolved (ConfigKeyKit
1.0.0-beta.3, MistKitConfiguration#1 merged, 1.0.0-beta.1 tagged) and its durable
hazards live in `.claude/memory/`. The five unfiled follow-ups it carried are
preserved on issue #407 rather than dropped.

Also supersedes the `resolveBool` directive — ConfigKeyKit#8 shipped in beta.3,
so that simplification is unblocked pending verification.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Packages/MistKitConfiguration` rides the `v1.0.0-beta.5` release branch so MistKit
and MistKitConfiguration can be developed together, then comes out in the
`v1.0.0-beta.5` → `main` release PR. Keeping it would be circular: a MistKit release
carrying a package whose `.gitrepo` tracks a branch pinning that same unreleased
release.

Records the retirement order so the removal isn't missed at release time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@leogdion
leogdion marked this pull request as ready for review August 31, 2026 22:23
…stra

Both lockfiles still recorded ConfigKeyKit as `branch: "main"`, so once
MistKitConfiguration moved to `from: "1.0.0-beta.3"` the requirement no longer
matched what was pinned. CI resolves with automatic resolution disabled and
failed with "an out-of-date resolved file was detected".

Same revision (3c8ae38) — only the pin's provenance changes, branch → version.
MistDemo was relocked in bc3c671; these two were missed.

Verified with `swift build --disable-automatic-resolution` (CI's flag) in all
three Examples.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@leogdion
leogdion merged commit 525e351 into v1.0.0-beta.5 Aug 31, 2026
67 of 69 checks passed
@leogdion
leogdion deleted the 407-mistkitconfiguration branch September 2, 2026 19:17
@claude claude Bot mentioned this pull request Sep 3, 2026
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