Repository navigation
v0.3.2
Closes #19.
A patch: nothing in the API list moved, and the payload version stays 2.0.0.
Configuration and traps
AethergramConfigurationclampstransmitIntervalandmaxBackoffIntervalto a ceiling far
past any real schedule and below about 9.2e18 seconds, where a sleep that runs traps. An interval
from there crashed the host at the first sleep that ran on it — the coalescing sleep its first
record scheduled wheneverbatchSizeis above 1, or a backoff sleep — and one past about 1.7e20
seconds, whereDuration.secondsitself traps, crashed wherever it was first made aDuration.
That configuration now runs, and the property reads back the clamped value. An interval that is
not finite, or not positive, still traps at construction, as in 0.3.1.queueLimitis not
capped, and one that is not positive still traps there too.TelemetryDeckTransportfails a precondition naming the backgroundURLSessionat the first
send over one. The send aborted the host at the same moment before, with anNSGenericException
about completion handlers that did not name the transport.- Calling back into the recorder from a seam it calls under its lock (
clientUserProvider,
environmentProvider, anyRetentionStorecall,SignalQueueStorage.load()) still terminates
the process, now through a precondition at the recorder entry point that was re-entered rather
than inside the lock's own recursion check.
Consent and erasure
- A pending erase is never superseded by a snapshot submitted after it. The queue writer keeps the
purge and the newest snapshot apart and runs the purge first, so a reset racing a record can no
longer leave a queue the store could not read on disk for a later grant. - A purge never creates the queue file. A decline before anything was granted, in a directory that
refuses the delete, used to be able to write one. - Across an erase, the cancelled send from before it may still be in flight while the drain after
it sends a different batch. The erase takes the drain claim instead of waiting for the cancelled
send to unwind. Sends were never promised to be serial.
Durability
- A queue file that could not be read is retried on every write. Once a read succeeds, what the
file held, up to the newestqueueLimitsignals, is kept ahead of the recorder's queue in every
write, for the next process to load and send. A store wrapped in another conformance keeps the
bound it already has: 1,000, unless it or a copy of it was also given to a recorder directly.
Previously the writes stopped for the life of the process. - The look for the erasure mark checks the mark's reachability and no longer reads file
attributes.
Retention counters and payload
- Day strings (
acquisition.firstSessionDateand the distinct-day history) areyyyy-MM-ddin the
Gregorian calendar, in the time zone of the calendar the recorder was given. A device set to
another calendar used to send its own year, such as a Buddhist year 543 ahead. A stored record
is converted when the recorder loads it: a day that already reads as a Gregorian date between
2015-01-01 and tomorrow is kept, any other is read in the recorder's calendar and kept converted
only if the result lands in that window, and otherwise kept as written. So a rebuilt or
downgraded record is never converted twice. A day written before 0.3.2 under the Ethiopic
calendar's Incarnation-era numbering that also reads as a Gregorian date in that window — any
day from Ethiopic year 2015 on outside the thirteenth month — cannot be told apart from a
Gregorian one and is left as written; one that does not, such as2018-13-01, is read as
Ethiopic like any other. One written under the Chinese or Dangi calendar in a leap month carries
no leap-month flag, so it converts to the same day of the ordinary month before it: 2025-07-25, written as0042-06-01, converts to 2025-06-25. - A session's first recorded signal after
beginSession()is checkpointed, so a session killed
inside the ten-second checkpoint interval is measured to that signal rather than discarded. endSession()closes only a session this recorder opened, and arecordadvances only that
session's checkpoint. A session left open by another process is closed by the next
beginSession()against its own last activity, rather than stretched to now.- For an app extension on iOS or Mac Catalyst (macOS reads no receipt),
EnvironmentSnapshot.current()resolves the receipt thatrunContext.channelis read from
against the containing app's bundle rather than the extension's own, falling back to the
extension's when the app's bundle gives no receipt location. The.appexhas to sit two
directory levels under the.app, as inPlugIns/orExtensions/; any other layout keeps the
extension's own. - A
calendar.hourOfDayvalue outside 0-23, which a caller can pass since caller parameters win,
goes to the wire unchanged instead of trapping the send. - A caller key spelled as a vendor wire name wins over the package key mapped onto that name, on
every run. The winner used to depend on dictionary order.
Scheduling and logging
- The drain runs at utility priority rather than the priority of the call that scheduled it.
- A queue overflow logs once when it begins and once when it ends, rather than on every record. It
ends when a delivery or a permanent rejection leaves the queue below the limit; an erase clears
it without the ending line. A store skipping writes logs when the skipping starts and when it
stops or its reason changes, rather than on every write.
Tooling
- CI builds the package for iOS release as extension-safe, and the core alone with no adapter in
reach. - The history scan forgives the pull request number in the subject GitHub writes on a merge
commit, and nowhere else. The scanner and the check runner are scanned line by line, with only
their exact fixture lines allowed.