Skip to content
C.W.K.
Lesson 05 of 05 · published

What a New Child Pays — and What It Gets for Free

~13 min · obligations, complete-bindings, protection-policy, extensibility

Level 0Idle Hands
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"A new child pays its own parsing, permission, change and undo obligations." — Tinker architecture

The Bill

Every capability that wants into Tinker's registry pays the same four obligations, in its own currency. Parsing: decode its own input from JSON, offer a harmless initialInput, and refuse bad input in validate before anything is saved. Permission: name every macOS permission it needs, so the engine can refuse before the first change rather than half-way. Change: prepare keyed, serialisable plans that mark already-satisfied targets with needsApply: false, and apply exactly one plan per call. Undo: reverse a change from its saved payload alone, report .noLongerApplicable when there is nothing left to reverse, and throw ChangeNotAttempted only with proof.

There is a fifth line on the bill for a particular kind of child. Tinker's first capability changes only application visibility; it never reads, types into, clicks or submits anything in another app's UI, and password and payment interfaces are entirely outside it. The architecture is explicit that any future capability touching application UI must implement its own protection policy before it may enter the registry. The broad Accessibility grant that makes hiding possible is not blanket permission to act.

The Free Part

In return, a child inherits everything that is hard to get right and identical for every action. The durable journal and its prepared/applied receipts. Shared ownership between overlapping presets. Rollback of confirmed changes when a later step fails. Emergency release, the two-step Quit and the restore-before-logout path. And — the part that makes the root pleasant to extend — a command, a shortcut slot, a tinker:// link and an app-door entry, before anyone writes any UI for it.

How a Command Appears From Nowhere

That last gift comes from completeBindings, which runs whenever settings load or are saved. For every registered capability that no command refers to yet, it appends a command whose ID is the capability's ID and whose operation is "run this capability with its initial input". For every preset it ensures the toggle, apply and restore commands exist, and it always ensures the Stop and restore everything command exists. Running a capability command is itself a toggle: its owner in the journal is command/<id>, so the first run applies and the next one restores that invocation.

The Cost of Saying No

The bill is deliberately not cheap. A capability that cannot describe its change before making it, or cannot undo from a receipt, does not get in — however useful it would be. That is the price of the sentence the whole design rests on: an actuator that might do things it cannot take back is no longer one Dad can hand a global shortcut to without thinking.

Code

completeBindings: every child and preset gets bindable commands as data·swift
extension Configuration {
  @MainActor public mutating func completeBindings(capabilities: [Capability]) {
    for capability in capabilities where !commands.contains(where: {
      if case .capability(let step) = $0.operation { return step.capability == capability.id }
      return false
    }) {
      commands.append(.init(id: capability.id, name: capability.name,
        operation: .capability(.init(capability: capability.id, input: capability.initialInput))))
    }
    for preset in presets {
      for (verb, operation) in Self.presetOperations(preset.id)
          where !commands.contains(where: { $0.operation == operation }) {
        commands.append(.init(id: preset.id + "." + verb,
          name: Self.generatedName(verb, preset.name), operation: operation))
      }
    }
    if !commands.contains(where: { $0.operation == .releaseAll }) {
      commands.append(.init(id: "release", name: "Stop and restore everything", operation: .releaseAll))
    }
  }
}
The bill and the gift, side by side·text
A NEW CHILD PAYS                       IT GETS FOR FREE
----------------                       ----------------
decode + validate its input            durable receipt journal
initialInput (valid, harmless)         shared ownership across presets
name its permissions                   rollback of confirmed steps
keyed, serialisable plans              emergency release + resume gate
needsApply for satisfied targets       restore on Quit / logout
apply one plan per call                a command + shortcut slot
undo from the saved payload            tinker://command/<id> link
ChangeNotAttempted only with proof     app-door command.run entry
UI-touching? its own protection        Activity history
  policy before registration

External links

Exercise

Someone proposes a capability that types a canned reply into the frontmost chat app. Walk it through the bill. Which obligations can it meet, which can it not, and what would its protection policy need to say before it could even be considered?
Hint
It can parse input and name permissions, but its undo is weak (deleting typed text depends on the field still being focused and unchanged) and its apply reads and writes another app's UI. A protection policy would at least need to refuse secure text fields, refuse when the target is not the expected app and field, and define what 'undo' even means for text that may already have been sent. Most such proposals fail the undo line before the policy question arises.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.