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
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 "