Skip to content

Releases: distronode-corporation/district-core-swift

8.1.0

Choose a tag to compare

@distronode-com distronode-com released this 25 Oct 08:00
Immutable release. Only release title and notes can be modified.
fdb2cc5

The shared core for District AI for iPhone 3.2.0 and its call Live Activity. Additive only; nothing is removed or renamed.

  • CallsRepository.registerLiveActivity(workspaceId:callId:token:) (POST /api/district/calls/{callId}/live-activity): registers a call's Live Activity push token, so the server can update the Lock Screen, the Dynamic Island and the Apple Watch Smart Stack.
  • CallsRepository.registerLiveActivityStartToken(workspaceId:token:) (POST /api/district/live-activity/start-token): registers the device's push-to-start token, so a ring can start the Live Activity on a locked phone.
  • Two contract fixtures, byte-identical to the service's.
  • The api-error codes from #41 (engine_unavailable, workflow_member_push_roles_invalid) with reviewed French.

Full notes: CHANGELOG.md [8.1.0].

8.0.0

Choose a tag to compare

@distronode-com distronode-com released this 25 Oct 00:41
Immutable release. Only release title and notes can be modified.
dabfa0f

The shared core for the Apple Watch round 2 of District AI for iPhone, iPad and Mac: a
person's decline reaches the server, a supervisor can whisper to the agent, and a
notification extension can name a message's sender. Nothing is removed or renamed, and it is a
major version because it is source-breaking (the pull request template's rule): ⚠️ three
public enums gain a case (IncomingCallEvent.withdrawnLocally,
IncomingCallCommand.requestDecline, RingCancelReason.declined), so an exhaustive switch
over the inbound command list (both apps' IncomingCallModel) or over a
DesktopRingEnd.cancelled(_:) reason (District AI for Mac's ringStopped) needs the new arm
when the pin moves.

Added

  • DistrictEndpoints.declineCall(callId:workspaceId:) (POST /api/district/calls/{id}/decline
    with {workspaceId}) and InboundCallRepository.decline(callId:workspaceId:), decoding
    SuccessResponse. Never retried.
  • DistrictEndpoints.whisper(workspaceId:callId:text:) (POST /api/district/calls/whisper
    with {workspaceId, callId, whisper}) and CallsRepository.whisper(workspaceId:callId:text:),
    decoding SuccessResponse. Never retried; a refusal keeps the server's code.
  • DistrictEndpoints.notifyToken() (POST /api/auth/native/notify-token with {}),
    NotifyTokenResponse (its token redacted from every description and the mirror) and
    NotifyTokenRepository.mint().
  • DistrictEndpoints.messageSender(workspaceId:id:) (GET /api/district/messages/{id}/sender),
    MessageSenderResponse and InboxRepository.messageSender(workspaceId:messageId:), which
    answers the trimmed name, or nil for a thread with no named contact.
  • EndpointID cases declineCall, whisper, notifyToken and messageSender, typed from
    their first commit (TypedEndpoints.liveCall, TypedEndpoints.notifyRead). The endpoint
    total is 124 and the typed count 122.
  • contracts/mobile/district-call-decline.json, district-message-sender.json and
    district-notify-token.json, from the service's contract, gated in ContractFixtureTests
    (ContractManifest.expectedFixtureCount is 190).
  • RingCancelReason.declined, the ring cancel the server sends once every member it rang has
    declined. Any other unknown value still reads as withdrawn.

Changed

  • ⛔ A person's decline is now reported (Sean's decision, 2026-10-24, GitLab #2 note
    4083623386). IncomingCallController appends .requestDecline(workspace:call:) to the
    shared ending when .declinePressed arrives while ringing, so the server can end the ring
    and the agent takes a message at once. A timeout, a withdrawal and a missing session still
    send nothing: the apps' withdraw and endForNoSession paths feed the new
    .withdrawnLocally, which ends the call exactly as .declinePressed did and emits no
    request. testDecliningAndTimingOutEmitByteIdenticalCommandLists is replaced by tests that
    pin both halves.
  • Comments that said an expired ring "falls back to PSTN" now say the agent takes a message.

7.1.0

Choose a tag to compare

@distronode-com distronode-com released this 23 Oct 07:46
Immutable release. Only release title and notes can be modified.
bedb557

The shared core for District AI for iPhone, iPad and Mac after 3.0.3. One public type is
added; nothing is removed or renamed.

Added

  • SchedulingWallClock, a working-hours time field's two directions: fieldText(_:) shows a
    stored HH:MM as « 9 h », « 9 h 30 » or « 17 h » in French (unchanged 09:00 in English),
    and wireValue(_:) turns a typed 9 h, 9 h 30, 9h30, 9:30 or 09:30 into the
    zero-padded HH:MM the scheduler requires, or nil for garbage or a clock outside
    00:00...23:59. The apps' Edit working hours sheet showed the wire value « 09:00 » on a
    French screen. Tested in SchedulingWallClockTests.

7.0.3

Choose a tag to compare

@distronode-com distronode-com released this 22 Oct 22:55
Immutable release. Only release title and notes can be modified.
6b32120

The shared core of District AI for iPhone, iPad and Mac 3.0.3. Nothing public is added,
removed or renamed.

Changed

  • contracts/mobile/district-voice-studio.json, contracts/i18n/api-errors.json and
    contracts/i18n/wire-vocabulary.json from monorepo ac4071d14, byte for byte: the Claude
    Haiku 5.5 brain entry, 44 new error codes (knowledge_url_*, member_phone_*,
    transfer_*, workflow_*), the portal's disposition labels, and new workflow triggers, an
    action and reason codes. Five of those reason codes (no-devices, no-message,
    push-lookup-failed, push-not-delivered, signing-unavailable) now have a label of their
    own in English and French, where they read as the code itself before ("No devices"). Their
    catalog entries and accessors are generated (scripts/gen-l10n.py), and the 61 new French
    entries are reviewed, each as the contract words it. The contract's French for the
    dispositions booked and voicemail is now « Rendez-vous pris » and « Boîte vocale », the
    words this catalog already had.
  • api_error.transfer_managed_only says « est accessible aux espaces de travail », not
    « est offert »: frcheck's STORE-APPLE rule reads an affirmative « offert » as an offer the
    English lacks. The service's French changed to the same words, and the entry's fr_variant
    records why.

Release guard (French sign-off): https://github.com/distronode-corporation/district-core-swift/actions/runs/39868325437

7.0.2

Choose a tag to compare

@distronode-com distronode-com released this 16 Oct 13:22
Immutable release. Only release title and notes can be modified.
5d03f80

The shared core of District AI for iPhone, iPad and Mac 3.0.2.

Changed

  • contracts/i18n/api-errors.json from monorepo b48ce94d4, byte for byte: the new code
    sending_domain_provider_changed (English and French), which the monorepo's
    drift:app_contracts was red on. Its catalog entry api_error.sending_domain_provider_changed
    and accessor are generated (scripts/gen-l10n.py). ⚠️ Its French is NOT the contract's
    verbatim: the contract says « les nouveaux enregistrements DNS », which frcheck's ban on
    « enregistrement » outside a denial refuses (an error, which cannot be waived), so the
    catalog says « les nouvelles entrées DNS », the words the service's French is moving to.
  • contracts/i18n/wire-vocabulary.json and format-vectors.json from monorepo ecf098046,
    byte for byte. New labels, generated into l10n/wire.toml with the fixture's French: the
    workflow skip reasons email-misconfigured (« Courriel mal configuré ») and ses-missing
    (« SES non configuré »; the English "SES missing" keeps the initialism in capitals, as
    District AI for Android writes it), the number status in-use ("Active", « Actif »), and the portal's
    headings for three research keys, estimatedValue "DGI Evaluation" (« Évaluation DGI »),
    person "Contact Profile" (« Profil du contact ») and callerIntel "Caller intelligence"
    (« Renseignements sur l'appelant »), which read as the key itself before.
  • A length in French no longer breaks between the minutes and the seconds:
    L10nFormat.minutesSeconds writes « 1 min 5 s », a no-break space (U+00A0)
    between each number and its unit and between the two parts, as the format vectors now say.
    It was « 1 min 5 s ».

7.0.1

Choose a tag to compare

@distronode-com distronode-com released this 16 Oct 01:25
Immutable release. Only release title and notes can be modified.
d7bf46d

Fixes found in the District AI for Mac 3.0.0 ring test (2026-10-15), and the ring cancel every
3.0.1 app reads. Nothing is removed or renamed. ⚠️ Two public enums gain a case
(TelemetryEventType.callRingCancelled, DesktopRingEnd.cancelled), so an exhaustive switch
over either needs the new arm; the only consumer of both is District AI for Mac, updated in
its 3.0.1, and district-ios switches over neither.

Changed

  • ⛔ The scheduling hand-off never mints UNBOUND (S33; Sean, GitLab #2 note 4022232351).
    SchedulingHandoffFlow used to fall back to a mint with no nonce when leg 1 did not
    call back within the clock, failed visibly, or the state was unusable; while any build
    could do that, the server could not switch HANDOFF_REQUIRE_NONCE on. Those paths now
    end as the new SchedulingHandoffOutcome.noCallback and send nothing; the app shows a
    notice and the person presses again. SchedulingHandoffClient.mint(workspaceId:next:nonce:)
    takes a required nonce: String, so no caller can mint without one. The callback clock
    is 30 s (was 10 s), since running out now costs a retry rather than an unbound page.
    ⚠️ Source-breaking for an app: an exhaustive switch over SchedulingHandoffOutcome
    needs the .noCallback arm (district-macos and district-ios both switch over it), and
    a call to mint without a nonce no longer compiles (neither app calls it).

Added

  • The ring cancel (docs/ring_cancel_contract.md in the service repository): when a ring the
    server started ends (answered on some device, timed out, the caller hung up, or withdrawn),
    the telemetry socket carries call_ring_cancelled {callId, reason} and the Apple alert
    rows get a background push {type: "call_ring_cancelled", workspaceId, callId, reason}.

    • TelemetryEventType.callRingCancelled and TelemetryEnvelope.ringCancelReason.
    • RingCancelReason (answered, timeout, call_ended, withdrawn; any other value,
      or none, is withdrawn).
    • RingCancel.parse(_:) and parse(userInfo:) for the push. PushPayload.parse still
      drops the type: it is never tapped.
    • DesktopRingGate stops a ring still ringing for that call with
      DesktopRingEnd.cancelled(reason). A call this device answered was cleared from the gate
      at the press, so the cancel it also receives changes nothing.
    • The contract fixture contracts/desktop/telemetry-event-call-ring-cancelled.json
      (14 desktop fixtures).
  • CallJoinGate (DistrictCall): whether a call's join is still on its way, for the
    microphone command that must not race it. The SDK reports a room connected before it
    publishes the microphone the join asked for, and both reducers answer .connected with
    an unmute; an unmute reaching the SDK in that gap found no microphone publication and
    published a second track (two microphone tracks on every answered Mac call, and the
    join's own publish then timed out waiting for its first audio frame). The engine asks
    admitsMute(_:) before every mute command: an unmute while the join is out is dropped
    (the join publishes the microphone live), a mute waits for the join and goes through only
    if it succeeded.

  • WireLabel.knownLabel(_:_:) (DistrictL10n): the vocabulary's label of a value in the
    active language, or nil when the vocabulary does not list it. For an English screen that
    shows the portal's word (Completed, not completed) and keeps an unlisted value as
    sent; a French screen keeps label(_:_:).

  • TokenRefreshCoordinator.finishRefreshInFlight(): waits for a refresh already on the
    wire, and the write of its successor, without starting one. For an app about to quit: a
    quit between the server's rotation and the store's write left the spent token on disk
    with the marker naming it, and the next launch could only report an interrupted refresh
    (the person signed out by pressing Quit). A successor the store refused earlier is
    written one last time too.

7.0.0

Choose a tag to compare

@distronode-com distronode-com released this 15 Oct 00:32
Immutable release. Only release title and notes can be modified.
1292c25

The core of District AI 3.0 for iPhone, iPad and Mac: every sentence the core writes in
English and reviewed Canadian French. It is the three 7.0.0 betas below, and these
changes since 7.0.0-beta.3.

What 7.0.0 brings over 6.0.0:

  • Languages. DistrictL10n: every sentence the core writes comes from a catalog, in
    English and reviewed Canadian French, and the app chooses which
    (L10nRuntime.locale). The service's error codes (ApiErrorText), stored values
    (WireLabel, 39 categories) and formats (L10nFormat: money, dates, times,
    durations, numbers) read as the service writes them, held to contracts/i18n/. English
    is byte-identical to 6.0.0.
  • Requests send Accept-Language, and ApiError.http carries the service's error
    code (http(status:message:code:)); localizedMessage reads it in the reader's
    language.
  • Account settings: AccountSettingsRepository reads the account's stored language
    and writes it (saveLocale).
  • Billing: price(cents:) on subscriptions and invoices names the currency in French
    (249,00 $ CA); English stays $249.00.
  • Webhooks: an editor offers the four booking events, as the web portal does, and a
    save keeps the events a webhook already has.
  • Contracts from monorepo 43eed5fd6: 187 mobile fixtures.
  • Breaking, for the apps: ApiError.http has a third value, WireCategory new cases,
    and several stored lists are computed; 7.0.0-beta.1 lists what the apps absorb.

Changed

  • The catalogs carry the v3.0 reviewed French.
  • README: 7.0.0 is the current release and the examples pin it; the GitLab mirror and
    project page links; the contract gate's 187 fixtures.
  • scripts/check-public-hygiene.py allows this repository's own GitLab mirror URL, and no
    other GitLab URL.
  • CHANGELOG: the date of 1.0.0.
  • CI checks that every catalog has its reviewed French (l10n.py check --require fr-CA),
    one catalog per call.

6.0.0

Choose a tag to compare

@distronode-com distronode-com released this 07 Oct 23:52
Immutable release. Only release title and notes can be modified.
270752f

The second factor after native Sign in with Apple. A major release (see Changed). Every
request 5.0.0 could make is byte-identical; what is new is one answer to the Apple exchange
and one route.

Added

  • POST /api/auth/native/apple now answers 401 mfa_required with a single-use ticket when
    the account has an authenticator enrolled (the service's S152, live since 2026-10-06).
    NativeAuthClient.exchangeAppleIdentityToken(_:) reads it as the new
    CodeExchangeResult.mfaRequired(NativeMfaChallenge) (the ticket, and its expiry parsed by
    WireInstant); any other 401 stays transportFailure, and the PKCE exchange never
    produces the case.
  • NativeAuthClient.submitMfaCode(_:) posts NativeMfaRequest (mfaTicket, code,
    deviceId, deviceName omitted when nil, platform) to POST /api/auth/native/mfa and
    answers NativeMfaResult: success with the unchanged five-key grant, invalidCode
    (401: wrong code or locked account, the ticket stands), ticketRejected (400: expired,
    spent, another install or the account changed; start again from the Apple sheet),
    rateLimited (429) or transportFailure.
  • NativeMfaChallenge.isExpired(at:), false when the expiry could not be read.
  • In DistrictModel: NativeMfaRequiredResponse, the 401's five keys.
  • In DistrictAuthCore: NativeMfaCode, the shape check the apps run before sending a code
    (six ASCII digits for an authenticator code; a recovery code of 8 to 64 characters that is
    not six digits, sent upper-cased), mirroring the service's routing by shape, and
    authenticatorInput(_:) for filtering a field as it is typed.
  • Contracts: contracts/mobile/ gains the service's two new fixtures (166 files, was 164),
    district-native-apple-mfa-required.json and district-native-mfa.json, both gated
    (NativeMfaRequiredResponse, and NativeTokenResponse, which is the first recorded native
    grant). ContractFixtureTests now depends on DistrictAuthCore, and
    DistrictNetworkTests on ContractGateSupport so the client tests read the recorded bytes.

Changed

  • ⚠️ Source-breaking: CodeExchangeResult gains a case, so an exhaustive switch over it
    needs .mfaRequired (both apps' AppleSignInController.exchange have one). That makes
    this release a major version.

5.0.0

Choose a tag to compare

@distronode-com distronode-com released this 06 Oct 14:40
Immutable release. Only release title and notes can be modified.
348bc96

A major release (see Changed). Every request 4.0.0 could make is byte-identical; what is
new is the socket's outbound side.

Added

  • The live call transcript (transcript v1 on the telemetry socket). In DistrictModel:
    TelemetryEventType gains transcriptSnapshot, transcriptSegment, transcriptEnded,
    transcriptRetracted and transcriptError (and isTranscript);
    TelemetryEnvelope.transcriptEvent reads their data into TranscriptEvent
    (TranscriptSnapshotData, TranscriptSegmentData, TranscriptEndedData,
    TranscriptRetractedData, TranscriptErrorData, with TranscriptSegment), refusing a
    v other than 1 and data whose callId disagrees with the envelope's. The speaker, end
    reason, retract reason and error code are open vocabularies (.other keeps an unknown
    name), unknown data keys are ignored, and a segment's text is left out of every
    printed form. TranscriptClientOp writes the three client ops
    (transcript.subscribe, transcript.unsubscribe, socket.mode), refusing a call id
    outside [A-Za-z0-9_-]{1,64}.
  • In DistrictLive: TelemetryConnection (and TelemetryConnectionRunner) keep a set of
    transcript subscriptions and send them again on every open, renewals included, after a
    socket.mode when built with broadcast: false; new events
    subscribeTranscript/unsubscribeTranscript/resubscribeTranscript, a new command
    send, and the runner's three matching methods. TranscriptReducer is the contract's
    client algorithm for one call as a pure state machine, following the contract's §4.6
    and its §4.12 clarifications: a high-water mark per epoch plus the set of missing seqs
    below it (a late frame fills its gap, anything else at or below the mark is a duplicate;
    the missing set is kept as ranges, so a jump of any size is tracked exactly), revisions,
    (epoch, index) order, a re-subscribe after a gap lasts 2 s with one heal in flight per
    call, snapshots whose parts arrive back to back with the same header (a broken one is
    asked for again once its last part is in) and which close every older epoch, retraction
    with tombstones (counted only when it carries epoch with seq), and the end:
    call_ended, handed_off or an unknown reason is final and fetches the full transcript
    with backoff; agent_error is the new LiveTranscriptPhase.reconnecting until a fresh
    assistant's epoch or the call's end; a snapshot's endedReason says the same (§4.12 Q9),
    so a fresh subscribe after agent_error is reconnecting too. A snapshot with no epoch
    follows an all: true purge (Q12): state and missing set are cleared, and the next frame
    of each epoch sets its baseline. not_live is final for its subscribe:
    callShownInProgress(atMilliseconds:) (a call-status signal that shows the call in
    progress, at most once per 30 s per call, Q10) is the only way to subscribe again, as the
    new TranscriptCommand.subscribe; after the 120 s eviction it is the path after the call
    (Q11). Only an error whose op is exactly
    transcript.subscribe changes anything.
  • TranscriptSnapshotData carries endedReason (null while live, absent from an older
    server and then nil), written back as an explicit null.
  • TranscriptRetractedData carries epoch beside seq (both null from the website),
    written back as explicit nulls; its initialiser takes the contract's key order.
  • Tests: the service's own fixtures for the five events (six frames, an interim segment
    among them) in Tests/TranscriptFrames/, copied byte for byte from its
    contracts/mobile/ set. They follow the contract revision with §4.12 Q1 to Q17 (the
    retraction's epoch, no empty opening snapshot, the snapshot's endedReason), and
    TranscriptFrameTests puts each through the strict gate and checks §4.12's invariants
    on them.
  • Contracts: contracts/mobile/ gains the service's seven new fixtures (164 files, was
    157): the six telemetry-event-transcript-* events and district-telemetry-token.json
    (the desktop file, byte for byte). ContractFixtureTests gates all seven, the events as
    the envelope and again as their typed data, with eight allowed-null paths (201, was
    193), and TranscriptFrameTests asserts its frames match the vendored copies byte for
    byte.

Changed

  • ⚠️ Source-breaking: TelemetryEventType gains five cases, so an exhaustive switch
    over it needs them (district-macos has none; DesktopRingGate's own is updated).
    TelemetrySocket gains a requirement, send(_:), which district-macos's
    URLSessionTelemetrySocket must implement; TelemetryConnectionEvent and
    TelemetryConnectionCommand gain cases. That makes this release a major version.
  • CI's floor on the number of tests run rises from 1850 to 1980.

4.0.0

Choose a tag to compare

@distronode-com distronode-com released this 05 Oct 06:52
Immutable release. Only release title and notes can be modified.
644a54f

A major release for one reason: DistrictSection gains a case (below). Every request
3.1.0 could make is byte-identical.

Added

  • District Studio links. AppLinkResolver maps /dashboard/district/studio (the web's
    section for the AI receptionist's settings since 2026-10-04) to the new
    DistrictSection.studio, and /dashboard/district/studio/<area> to it with the area
    carried: StudioArea names the pages the apps draw (persona, voice,
    call-handling, skills, knowledge), read back through
    AppLinkDestination.studioArea. A Studio page outside that list (integrations,
    video, anything else) resolves to openInBrowser. A Studio destination waits for a
    workspace.

Changed

  • Contract fixtures only, no change to the package: district-enrich-disabled.json and
    district-voice-studio.json mirror the service's new copies. The enrichment refusal now
    names District Studio → Integrations, and the Voice Studio read's heading is "Voice",
    the name of the web's District Studio page.
  • ⚠️ Source-breaking: DistrictSection gains a case, so an exhaustive switch over it
    (district-ios's AppLinkRouting) needs a .studio arm when it adopts this release;
    district-macos has no such switch and needs only the pin. That
    makes the next release a major version.
  • Repository only, no change to the package: the README carries the OpenSSF Scorecard
    badge, links district-macos and drops the private-era CI note; .github/SUPPORT.md says
    where questions and bugs go; the issue chooser links the README, Discussions and private
    vulnerability reporting; CodeQL runs gain a workflow-level concurrency group; and a
    new dependency-review.yml checks the dependencies a pull request adds.