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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import KeyPathRulesCore
///
/// Simple flat validation — iterates each pack's requirements and checks
/// if they're met. No graph traversal needed for ~15 packs with 1-2 depth chains.
@MainActor
/// Pure: it reads only the immutable registry, so any actor may ask.
enum PackDependencyChecker {
/// Returns unmet hard requirements for a specific pack.
static func unmetRequirements(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import KeyPathRulesCore
/// Phase 3 of docs/architecture/rules-admission-review.md; every caller (toggle,
/// editor, pack install, save gate, startup, doctor) asks it. Conflicts are
/// computed here from claims — what each rule puts on which slot — attributed
/// by rule id, never by display name (§4.4g).
@MainActor
/// by rule id, never by display name (§4.4g). Pure: value types in, verdicts
/// out, no actor; the save gate, the CLI and the UI all call it directly.
enum AdmissionEngine {
struct State {
var collections: [RuleCollection]
Expand Down Expand Up @@ -97,9 +97,9 @@ enum AdmissionEngine {

/// Every conflict in `collections` (enabled ones; custom rules already folded
/// in as collections) plus the leader key. Pure, so the save gate and the CLI
/// call it off the main actor. Order is the slot order of the claims; callers
/// call it directly. Order is the slot order of the claims; callers
/// that need a total order sort (see `mappingConflicts`).
nonisolated static func conflicts(in collections: [RuleCollection], leaderKey: LeaderKeyPreference? = nil)
static func conflicts(in collections: [RuleCollection], leaderKey: LeaderKeyPreference? = nil)
-> [AdmissionVerdict]
{
let owners: [(subject: AdmissionSubject, collection: RuleCollection)] =
Expand All @@ -108,7 +108,7 @@ enum AdmissionEngine {
}

/// The same, for the save gate and the CLI, in the error type they report.
nonisolated static func mappingConflicts(in collections: [RuleCollection], leaderKey: LeaderKeyPreference? = nil)
static func mappingConflicts(in collections: [RuleCollection], leaderKey: LeaderKeyPreference? = nil)
-> [KeyPathError.MappingConflictInfo]
{
let names = Dictionary(collections.map { ($0.id, $0.name) }, uniquingKeysWith: { first, _ in first })
Expand Down Expand Up @@ -142,8 +142,8 @@ enum AdmissionEngine {
conflicts(owners: owners, leaderKey: state.leaderKey)
}

private nonisolated static func conflicts(owners: [(subject: AdmissionSubject, collection: RuleCollection)],
leaderKey: LeaderKeyPreference?) -> [AdmissionVerdict]
private static func conflicts(owners: [(subject: AdmissionSubject, collection: RuleCollection)],
leaderKey: LeaderKeyPreference?) -> [AdmissionVerdict]
{
var verdicts: [AdmissionVerdict] = []
func conflict(_ key: String, _ layer: String, _ stakes: [Stake], subject: AdmissionSubject? = nil) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct RuleCollectionCatalog {
/// rules list and the toggle all say the same thing. Only the backend's
/// own limits count here: whether a companion app is installed is a
/// question for install or toggle time, with the user's real files.
@MainActor static func driverlessUnavailableReason(for id: UUID, profile: BackendCapabilityProfile = .shared) -> String? {
static func driverlessUnavailableReason(for id: UUID, profile: BackendCapabilityProfile = .shared) -> String? {
guard let collection = installedDefinitions.first(where: { $0.id == id }) else { return nil }
var enabled = collection
enabled.isEnabled = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ final class BackendCapabilityProfileTests: XCTestCase {
XCTAssertFalse(profile.supportsDeviceTargeting)
}

@MainActor
func testFormerHardCodedListsGiveTheSameAnswersThroughTheBridge() throws {
BackendCapabilityProfile.testOverride = try bridgeProfile()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ final class SessionCapsGeneratedConfigTests: XCTestCase {
if case .valid = admission.result { XCTFail("Unsupported media output must remain rejected") }
}

@MainActor
func testEveryBuiltInDefaultWithLeaderIsAdmitted() throws {
try SessionBridgeTestFixture.requireAvailable()
let catalog = RuleCollectionCatalog().defaultCollections()
Expand Down
4 changes: 4 additions & 0 deletions docs/architecture/rules-admission-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,10 @@ carries names and details rather than ids, and lives in KeyPathCore where the ve
types cannot be seen. It is produced from verdicts in exactly one place
(`AdmissionEngine.mappingConflicts`).

The engine and `PackDependencyChecker` are plain enums with no actor: both only read
value types and the immutable registry. `driverlessUnavailableReason` lost the
main-actor annotation that had followed from the engine's.

### 4.5 Lowering: intent in rules, backend output in the generator

Rules keep expressing intent (`volu`, `caps` → Hyper, "this keyboard only").
Expand Down
Loading