import: finalize/done in place of clear/cleared
Amends the vocabulary the same-day plan record describes, before
anything has run under it.

Assisted-by: GLM-5.3 via pi
change vzxuktxspltstrtqzlomxqtqxoysyvyt
commit 5cf919e443fc963c2a8c9e84e6abf17688a14d87
author Alpha Chen <alpha@kejadlen.dev>
date
parent yrvoloym
diff --git a/AGENTS.md b/AGENTS.md
index c2986d7..c4b8618 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -31,14 +31,15 @@ rake console          # irb with the store open on the configured database
                       # safety net for what the session might do there
 rake import:macos:plan # Plan importing this Mac's iCloud contacts
                       # (LIMIT=n; reads Contacts, so leave it to the user)
-rake import:macos:clear # Clear this Mac of the contacts the oldest
-                      # landed plan imported, filing the plan away
+rake import:macos:finalize # Finalize the contacts the oldest
+                      # landed plan imported, taking them off this
+                      # Mac and filing the plan away done
                       # (PLAN=dir for another; writes Contacts, so
                       # leave it to the user)
 rake import:execute   # Land the oldest plan still to land on a host
                       # (HOST=https://contacts; PLAN=dir for another)
 rake import:status    # Summarize the plans in data/import and what
-                      # execute and clear would carry next
+                      # execute and finalize would carry next
 rake profile:install  # Download carddav.mobileconfig from the app and
                       # stage it for approval
 ```
diff --git a/docs/plans/2026-09-19-import-under-data-import.md b/docs/plans/2026-09-19-import-under-data-import.md
index fc0f0ad..f9fc27e 100644
--- a/docs/plans/2026-09-19-import-under-data-import.md
+++ b/docs/plans/2026-09-19-import-under-data-import.md
@@ -22,22 +22,23 @@ mkdir -p data/import/plans data/import/done
 mv data/imports/* data/import/plans/
 ```
 
-## Clearing, not removing
+## Finalizing, not removing
 
 The step that takes a plan's contacts off this Mac was `remove`, an
 action's name, and its result was a contact `removed`. The vocabulary
-now names the state: `rake import:macos:clear` clears this Mac of a
-plan's contacts, and a contact's final status is `cleared` — on the
-host, in its groups, off this Mac. `Import::Remove` is `Import::Clear`.
+now names the state: `rake import:macos:finalize` finalizes a plan's
+contacts — off this Mac — and a contact's final status is `done`: on
+the host, in its groups, off this Mac. `Import::Remove` is
+`Import::Finalize`.
 
-Statuses already recorded as `removed` read as `cleared` (`Plan`
-carries the old spelling beside the new), so a plan built before the
-rename still finishes and files away; nothing is rewritten on disk.
+Statuses already recorded as `removed` read as `done` (`Plan` carries
+the old spelling beside the new), so a plan built before the rename
+still finishes and files away; nothing is rewritten on disk.
 
 ## A finished plan files away
 
-A plan whose every contact is cleared is done (`Plan#done?`). The
-clear task files one away into `done/` as its last step, and the
+A plan whose every contact is done is finished (`Plan#done?`). The
+finalize task files one away into `done/` as its last step, and the
 status read sweeps any a dead run left in `plans/` before listing —
 so what status lists is what still has work, and the finished plans
 are out of the way but kept, with their cards and sources, for as
diff --git a/lib/pro_tacts/import/card.rb b/lib/pro_tacts/import/card.rb
index 3907d0f..135ed09 100644
--- a/lib/pro_tacts/import/card.rb
+++ b/lib/pro_tacts/import/card.rb
@@ -23,9 +23,9 @@ module ProTacts
       KEYS = %w[first last nickname birthday phones emails addresses note photo groups source].freeze #: Array[String]
 
       # What the source gave, kept in the card rather than off to the
-      # side: the fields above are read out of it, and `clear` compares
-      # the Mac's contact against it before deleting anything, so the two
-      # are read together and edited in one place.
+      # side: the fields above are read out of it, and `finalize`
+      # compares the Mac's contact against it before deleting anything,
+      # so the two are read together and edited in one place.
       SOURCE_KEYS = %w[identifier vcard note contact].freeze #: Array[String]
 
       # An address's parts, as the editor's own add row names them
diff --git a/lib/pro_tacts/import/clear.rb b/lib/pro_tacts/import/finalize.rb
similarity index 84%
rename from lib/pro_tacts/import/clear.rb
rename to lib/pro_tacts/import/finalize.rb
index 193f2f9..0e89f8c 100644
--- a/lib/pro_tacts/import/clear.rb
+++ b/lib/pro_tacts/import/finalize.rb
@@ -2,12 +2,12 @@ require "pro_tacts/import/plan"
 
 module ProTacts
   module Import
-    # Clears this Mac of a plan's originals, once they have landed on
-    # a host (docs/plans/2026-09-16-importing-from-macos.md, "Removing
-    # the originals"). Every check is a reason to keep a contact rather
-    # than to delete one, because a wrong keep costs a second look and a
-    # wrong delete costs the contact.
-    class Clear
+    # Finalizes a plan's contacts off this Mac, once they have landed
+    # on a host (docs/plans/2026-09-16-importing-from-macos.md,
+    # "Removing the originals"). Every check is a reason to keep a
+    # contact rather than to delete one, because a wrong keep costs a
+    # second look and a wrong delete costs the contact.
+    class Finalize
       # @rbs @plan: Plan
       # @rbs @client: _Client
       # @rbs @mac: _Mac
@@ -20,7 +20,7 @@ module ProTacts
       # find in Contacts and a line to find in plan.yml. Signed in
       # sig/pro_tacts/import.rbs, being a Data class.
       # @rbs skip
-      Result = Data.define(:cleared, :kept)
+      Result = Data.define(:done, :kept)
 
       #: (Plan plan, client: _Client, mac: _Mac) -> Result
       def self.call(plan, client:, mac:)
@@ -39,9 +39,9 @@ module ProTacts
         imported = @plan.with_status("imported")
         records = @mac.show(imported.map(&:source_id))
         gone, still_there = imported.partition { !records.key?(it.source_id) }
-        # A contact this Mac no longer has is a contact cleared, which is
+        # A contact this Mac no longer has is a contact done, which is
         # what a rerun of an interrupted run sees.
-        gone.each { @plan.record(it.id, Plan::CLEARED) }
+        gone.each { @plan.record(it.id, Plan::DONE) }
 
         kept = [] #: Array[[String, String, String]]
         going = still_there.select { |contact|
@@ -55,12 +55,12 @@ module ProTacts
         # the plan records the rest finds them gone next time.
         refused = @mac.delete(going.map(&:source_id))
         deleted, stuck = going.partition { !refused.key?(it.source_id) }
-        deleted.each { @plan.record(it.id, Plan::CLEARED) }
+        deleted.each { @plan.record(it.id, Plan::DONE) }
         stuck.each {
           kept << [it.id, it.name, "this Mac would not delete #{it.source_id}: #{refused.fetch(it.source_id)}"]
         }
 
-        Result.new(cleared: gone.size + deleted.size, kept:)
+        Result.new(done: gone.size + deleted.size, kept:)
       end
 
       private
diff --git a/lib/pro_tacts/import/macos.rb b/lib/pro_tacts/import/macos.rb
index 6c5f41c..e74abe2 100644
--- a/lib/pro_tacts/import/macos.rb
+++ b/lib/pro_tacts/import/macos.rb
@@ -68,8 +68,8 @@ module ProTacts
         run("read", *[limit&.to_s].compact)
       end
 
-      # This Mac as `Clear` speaks to it: the contacts it still has, by
-      # source id, and the delete that takes them off it.
+      # This Mac as `Finalize` speaks to it: the contacts it still
+      # has, by source id, and the delete that takes them off it.
       module Mac
         #: (Array[String] source_ids) -> Hash[String, untyped]
         def self.show(source_ids)
diff --git a/lib/pro_tacts/import/plan.rb b/lib/pro_tacts/import/plan.rb
index 5afebfa..94547c8 100644
--- a/lib/pro_tacts/import/plan.rb
+++ b/lib/pro_tacts/import/plan.rb
@@ -8,7 +8,7 @@ require "pro_tacts/import/card"
 module ProTacts
   module Import
     # One import, as a directory: what a source builder decided, and how
-    # far execute and clear have carried it
+    # far execute and finalize have carried it
     # (docs/plans/2026-09-16-importing-from-macos.md).
     class Plan
       # @rbs @dir: Pathname
@@ -22,7 +22,7 @@ module ProTacts
       Entry = Data.define(:id, :source_id, :card)
 
       # A contact as the plan lists it, with the last step it reached:
-      # nil, then landed, imported, and cleared, in that order. The name is
+      # nil, then landed, imported, and done, in that order. The name is
       # the card's when the plan was built, written down so a person
       # reading plan.yml can tell which line is whose without opening a
       # card; nothing reads it back to decide anything.
@@ -37,18 +37,18 @@ module ProTacts
       # A contact's final state: on the host, in its groups, and off
       # this Mac. A plan whose every contact has reached it is done and
       # files away under done/ (tasks/import.rake).
-      CLEARED = "cleared" #: String
+      DONE = "done" #: String
 
-      # The spelling of CLEARED plans recorded before the state was
+      # The spelling of DONE plans recorded before the state was
       # named for what it is; read as it so a plan built then files
       # away now.
       REMOVED = "removed" #: String
 
       # Whether the plan has carried every contact through: nothing
-      # left for execute or clear to do.
+      # left for execute or finalize to do.
       #: () -> bool
       def done?
-        contacts.all? { [CLEARED, REMOVED].include?(it.status) }
+        contacts.all? { [DONE, REMOVED].include?(it.status) }
       end
 
       #: (Pathname dir, source: String, created_at: Time, entries: Array[Entry]) -> Plan
diff --git a/sig/pro_tacts/import.rbs b/sig/pro_tacts/import.rbs
index 22ec5e3..90c8788 100644
--- a/sig/pro_tacts/import.rbs
+++ b/sig/pro_tacts/import.rbs
@@ -43,12 +43,12 @@ module ProTacts
       end
     end
 
-    class Clear
+    class Finalize
       class Result
-        attr_reader cleared: Integer
+        attr_reader done: Integer
         attr_reader kept: Array[[String, String, String]]
 
-        def self.new: (cleared: Integer, kept: Array[[String, String, String]]) -> instance
+        def self.new: (done: Integer, kept: Array[[String, String, String]]) -> instance
       end
     end
 
diff --git a/tasks/import.rake b/tasks/import.rake
index 411c4d5..413d87d 100644
--- a/tasks/import.rake
+++ b/tasks/import.rake
@@ -23,7 +23,7 @@ module ImportTasks
   # that step — named so a status read can say what those tasks would
   # pick next without restating their choice.
   TO_LAND = ->(plan) { plan.with_status(nil).any? || plan.with_status("landed").any? }
-  TO_CLEAR = ->(plan) { plan.with_status("imported").any? }
+  TO_FINALIZE = ->(plan) { plan.with_status("imported").any? }
 
   # The plan PLAN names, or the oldest one still waiting for this step:
   # plans are carried in the order they were built, so the next one to
@@ -43,8 +43,8 @@ module ImportTasks
   end
 
   # A finished plan's final state: moved under DONE, nothing left to
-  # carry. The clear task files one away as its last step; the status
-  # read sweeps any a dead run left behind.
+  # carry. The finalize task files one away as its last step; the
+  # status read sweeps any a dead run left behind.
   def self.file_away(plan)
     FileUtils.mkdir_p(DONE)
     FileUtils.mv(plan.dir, DONE / plan.dir.basename)
@@ -73,27 +73,27 @@ namespace :import do
       abort error.message
     end
 
-    desc "Clear this Mac of the contacts the oldest landed plan imported, filing the plan away once its last contact is off (PLAN=dir for another)"
-    task :clear do
+    desc "Finalize the contacts the oldest landed plan imported, taking them off this Mac and filing the plan away done (PLAN=dir for another)"
+    task :finalize do
       require "net/http"
       require "uri"
       require "pro_tacts/import/http_client"
       require "pro_tacts/import/macos"
-      require "pro_tacts/import/clear"
+      require "pro_tacts/import/finalize"
 
-      plan = ImportTasks.plan(ImportTasks::TO_CLEAR, "to clear")
+      plan = ImportTasks.plan(ImportTasks::TO_FINALIZE, "to finalize")
       host = plan.host or abort("#{plan.dir} has not landed on a host")
       uri = URI.parse(host)
-      puts "clearing the contacts #{plan.dir} landed on #{host}"
+      puts "finalizing the contacts #{plan.dir} landed on #{host}"
 
-      result = nil #: ProTacts::Import::Clear::Result?
+      result = nil #: ProTacts::Import::Finalize::Result?
       Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
-        result = ProTacts::Import::Clear.call(
+        result = ProTacts::Import::Finalize.call(
           plan, client: ProTacts::Import::HttpClient.new(http), mac: ProTacts::Import::Macos::Mac
         )
       end
       result.kept.each { |id, name, why| puts "kept #{id} #{name}: #{why}" }
-      puts "#{result.cleared} contacts cleared from this Mac"
+      puts "#{result.done} contacts finalized"
       if plan.done?
         ImportTasks.file_away(plan)
         puts "filed #{plan.dir.basename} away in #{ImportTasks::DONE}"
@@ -101,7 +101,7 @@ namespace :import do
     end
   end
 
-  desc "Summarize the plans in data/import and what execute and clear would carry next"
+  desc "Summarize the plans in data/import and what execute and finalize would carry next"
   task :status do
     require "pro_tacts/import/plan"
 
@@ -129,9 +129,9 @@ namespace :import do
 
     puts ""
     landing = plans.find(&ImportTasks::TO_LAND)
-    clearing = plans.find(&ImportTasks::TO_CLEAR)
+    finalizing = plans.find(&ImportTasks::TO_FINALIZE)
     puts "next: import:execute would land #{landing.dir.basename}" if landing
-    puts "next: import:macos:clear would finish #{clearing.dir.basename}" if clearing
+    puts "next: import:macos:finalize would finish #{finalizing.dir.basename}" if finalizing
   end
 
   desc "Land the oldest plan in data/import/plans still to land, or the one in PLAN, on the pro-tacts at HOST, a base URL such as https://contacts"
diff --git a/test/pro_tacts/import/test_clear.rb b/test/pro_tacts/import/test_finalize.rb
similarity index 81%
rename from test/pro_tacts/import/test_clear.rb
rename to test/pro_tacts/import/test_finalize.rb
index c7575fb..25dcc67 100644
--- a/test/pro_tacts/import/test_clear.rb
+++ b/test/pro_tacts/import/test_finalize.rb
@@ -6,16 +6,16 @@ require "tmpdir"
 
 require "pro_tacts/import/execute"
 require "pro_tacts/import/plan"
-require "pro_tacts/import/clear"
+require "pro_tacts/import/finalize"
 require "pro_tacts/web"
 
-class ImportClearTest < Minitest::Test
+class ImportFinalizeTest < Minitest::Test
   include ThrowawayContacts
 
   Card = ProTacts::Import::Card
   Plan = ProTacts::Import::Plan
   Execute = ProTacts::Import::Execute
-  Clear = ProTacts::Import::Clear
+  Finalize = ProTacts::Import::Finalize
 
   HOST = "https://contacts"
   IDS = %w[kmnuqmzxylru vmnlryyvktux].freeze
@@ -52,7 +52,7 @@ class ImportClearTest < Minitest::Test
     {"identifier" => source_id(id), "vcard" => vcard(id), "note" => note}
   end
 
-  # A plan already landed on the host, since that is what clear works on.
+  # A plan already landed on the host, since that is what finalize works on.
   def with_landed_plan(notes: {}, edit: nil)
     Dir.mktmpdir do |tmp|
       dir = Pathname.new(tmp) / "plan"
@@ -76,12 +76,12 @@ class ImportClearTest < Minitest::Test
     with_landed_plan do |plan, _store, client|
       mac = Mac.new(IDS.to_h { [source_id(it), record(it)] })
 
-      result = Clear.call(plan, client:, mac:)
+      result = Finalize.call(plan, client:, mac:)
 
-      assert_equal 2, result.cleared
+      assert_equal 2, result.done
       assert_empty result.kept
       assert_equal IDS.map { source_id(it) }, mac.deleted
-      assert_equal %w[cleared cleared], Plan.read(plan.dir).contacts.map(&:status)
+      assert_equal %w[done done], Plan.read(plan.dir).contacts.map(&:status)
     end
   end
 
@@ -91,11 +91,11 @@ class ImportClearTest < Minitest::Test
       records[source_id(IDS.first)]["vcard"] = "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Renamed\r\nEND:VCARD\r\n"
       mac = Mac.new(records)
 
-      result = Clear.call(plan, client:, mac:)
+      result = Finalize.call(plan, client:, mac:)
 
       assert_equal [[IDS.first, "Contact #{IDS.first}", "it has changed on this Mac since the plan"]], result.kept
       assert_equal [source_id(IDS.last)], mac.deleted
-      assert_equal ["imported", "cleared"], Plan.read(plan.dir).contacts.map(&:status)
+      assert_equal ["imported", "done"], Plan.read(plan.dir).contacts.map(&:status)
     end
   end
 
@@ -105,7 +105,7 @@ class ImportClearTest < Minitest::Test
       records[source_id(IDS.first)]["note"] = "Analyst. Also a countess."
       mac = Mac.new(records)
 
-      result = Clear.call(plan, client:, mac:)
+      result = Finalize.call(plan, client:, mac:)
 
       assert_equal [[IDS.first, "Contact #{IDS.first}", "its note has changed on this Mac since the plan"]], result.kept
       assert_equal [source_id(IDS.last)], mac.deleted
@@ -117,7 +117,7 @@ class ImportClearTest < Minitest::Test
       store.delete(IDS.first)
       mac = Mac.new(IDS.to_h { [source_id(it), record(it)] })
 
-      result = Clear.call(plan, client:, mac:)
+      result = Finalize.call(plan, client:, mac:)
 
       assert_equal [[IDS.first, "Contact #{IDS.first}", "its card is no longer on #{HOST}"]], result.kept
       assert_equal [source_id(IDS.last)], mac.deleted
@@ -131,22 +131,22 @@ class ImportClearTest < Minitest::Test
       refute_includes store.book("test@example.com"), IDS.first
       mac = Mac.new(IDS.to_h { [source_id(it), record(it)] })
 
-      result = Clear.call(plan, client:, mac:)
+      result = Finalize.call(plan, client:, mac:)
 
       assert_empty result.kept
       assert_equal IDS.map { source_id(it) }, mac.deleted
     end
   end
 
-  def test_a_contact_already_off_the_mac_counts_as_cleared
+  def test_a_contact_already_off_the_mac_counts_as_done
     with_landed_plan do |plan, _store, client|
       mac = Mac.new({source_id(IDS.last) => record(IDS.last)})
 
-      result = Clear.call(plan, client:, mac:)
+      result = Finalize.call(plan, client:, mac:)
 
-      assert_equal 2, result.cleared
+      assert_equal 2, result.done
       assert_equal [source_id(IDS.last)], mac.deleted
-      assert_equal %w[cleared cleared], Plan.read(plan.dir).contacts.map(&:status)
+      assert_equal %w[done done], Plan.read(plan.dir).contacts.map(&:status)
     end
   end
 
@@ -157,23 +157,23 @@ class ImportClearTest < Minitest::Test
     with_landed_plan do |plan, _store, client|
       mac = Mac.new(IDS.to_h { [source_id(it), record(it)] }, refuses: {source_id(IDS.first) => "faulting, 134092"})
 
-      result = Clear.call(plan, client:, mac:)
+      result = Finalize.call(plan, client:, mac:)
 
-      assert_equal 1, result.cleared
+      assert_equal 1, result.done
       assert_equal [[IDS.first, "Contact #{IDS.first}", "this Mac would not delete #{source_id(IDS.first)}: faulting, 134092"]], result.kept
       assert_equal [source_id(IDS.last)], mac.deleted
-      assert_equal ["imported", "cleared"], Plan.read(plan.dir).contacts.map(&:status)
+      assert_equal ["imported", "done"], Plan.read(plan.dir).contacts.map(&:status)
     end
   end
 
-  def test_a_rerun_clears_nothing_again
+  def test_a_rerun_finalizes_nothing_again
     with_landed_plan do |plan, _store, client|
       mac = Mac.new(IDS.to_h { [source_id(it), record(it)] })
-      Clear.call(plan, client:, mac:)
+      Finalize.call(plan, client:, mac:)
 
-      result = Clear.call(Plan.read(plan.dir), client:, mac:)
+      result = Finalize.call(Plan.read(plan.dir), client:, mac:)
 
-      assert_equal 0, result.cleared
+      assert_equal 0, result.done
       assert_equal IDS.map { source_id(it) }, mac.deleted
     end
   end
diff --git a/test/pro_tacts/import/test_plan.rb b/test/pro_tacts/import/test_plan.rb
index b4dd22b..68ae2b3 100644
--- a/test/pro_tacts/import/test_plan.rb
+++ b/test/pro_tacts/import/test_plan.rb
@@ -133,14 +133,14 @@ class ImportPlanTest < Minitest::Test
     end
   end
 
-  def test_a_plan_is_done_once_every_contact_is_cleared
+  def test_a_plan_is_done_once_every_contact_reaches_done
     in_tmpdir do |dir|
       plan = Plan.write(dir, source: "macos", created_at: CREATED_AT, entries: [entry("kmnuqmzxylru"), entry("vmnlryyvktux")])
 
       refute plan.done?
-      plan.record("kmnuqmzxylru", Plan::CLEARED)
+      plan.record("kmnuqmzxylru", Plan::DONE)
       refute plan.done?
-      plan.record("vmnlryyvktux", Plan::CLEARED)
+      plan.record("vmnlryyvktux", Plan::DONE)
       assert plan.done?
     end
   end
diff --git a/test/tasks/test_import.rb b/test/tasks/test_import.rb
index c14f319..d5d30c3 100644
--- a/test/tasks/test_import.rb
+++ b/test/tasks/test_import.rb
@@ -20,13 +20,13 @@ class ImportStatusTaskTest < Minitest::Test
       plans = root / "data" / "import" / "plans"
       # A plan carried all the way off the Mac — done, so swept into
       # done/ rather than listed — one mid-flight with a contact through
-      # to the Mac-clearing step and one past it, and one freshly built
+      # to the finalizing step and one past it, and one freshly built
       # with everything to do.
       through = Plan.write(plans / "macos-20260831T000000Z", source: "macos", created_at: Time.utc(2026, 8, 31), entries: [entry("qxqmqmqmqmqm"), entry("zzzzzzzzzzzz")])
-      through.contacts.each { it.status or through.record(it.id, Plan::CLEARED) }
+      through.contacts.each { it.status or through.record(it.id, Plan::DONE) }
       underway = Plan.write(plans / "macos-20260901T000000Z", source: "macos", created_at: Time.utc(2026, 9, 1), entries: [entry("kmnuqmzxylru"), entry("vmnlryyvktux")])
       underway.record("kmnuqmzxylru", "imported")
-      underway.record("vmnlryyvktux", Plan::CLEARED)
+      underway.record("vmnlryyvktux", Plan::DONE)
       Plan.write(plans / "macos-20260916T180412Z", source: "macos", created_at: Time.utc(2026, 9, 16, 18, 4, 12), entries: [entry("aaaaaaaaaaaa")])
 
       out, = run_task(root)
@@ -34,7 +34,7 @@ class ImportStatusTaskTest < Minitest::Test
       assert_includes out, "macos-20260901T000000Z  2/2 contacts\n"
       assert_includes out, "macos-20260916T180412Z  0/1 contacts\n"
       assert_includes out, "next: import:execute would land macos-20260916T180412Z\n"
-      assert_includes out, "next: import:macos:clear would finish macos-20260901T000000Z\n"
+      assert_includes out, "next: import:macos:finalize would finish macos-20260901T000000Z\n"
       assert (root / "data/import/done/macos-20260831T000000Z/plan.yml").file?
       refute (plans / "macos-20260831T000000Z").directory?
       refute_includes out, "macos-20260831T000000Z  "