Skip to content

v0.67.0

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 06 Sep 02:08
· 39 commits to main since this release

Removed

  • isSyncConflictError, isSyncNotFoundError, isUnknownEpochError and the
    SyncStatus type from ./sync. They ship from @interop/was-client/sync,
    beside the classes whose name they match, and are imported from there.
    Nothing is re-exported here: one owner per name. isKeyUnwrapError stays in
    ./descriptors, since it classifies a roster-membership failure the wallet
    layer owns and no sync driver dispatches on it.

Changed

  • The @interop/was-client dependency now requires ^0.49.0, for the
    predicates and SyncStatus.
  • deriveUnlockIdentity and unlockIdentityFromSeed state their return type
    explicitly, and UnlockIdentity is an interface rather than an alias over the
    inferred return. Same shape; the emitted declarations now name this package's
    own copy of the agent types.
  • decisions/0009 amended (revisit criterion 1 fired) and decisions/0021
    added: the engine and the RxDB driver stay two algorithms.

Removed

  • The three stages that re-sealed a SIBLING credential's unlock record. Every
    record's frame proof is signed by its own credential's unlock identity key,
    and its bridge and delegatedClients sibling delegations by that credential's
    own ladder VM (decisions/0019), so no ceremony owes a re-seal; each stage
    also planted the acting session's key as the sibling record's frame signer,
    which a later strike of that key turns into a lock-out no self-heal reaches.
    Gone with them:
    • remintRecoveryDelegations, RecordRemintOutcome and
      RecoveryDelegationEntry (/recovery) -- the shared re-mint core.
      delegateLogWrite, recordedDelegationFields, zcapExpiring,
      ZCAP_RENEWAL_WINDOW_MS and RECOVERY_DELEGATION_TTL_MS stay: a
      credential's own login still refreshes its own bridge on those axes.
    • revokeAccountClient's remintRecoveryDelegations option and the
      recovery member of ClientRevocationResult (/clients). The
      remintGenerationDelegation option is unaffected and is now stage 4.
    • retireUnlockCredential's remintDependentRecords option and the
      dependentRecords member of UnlockCredentialRetirementResult (/unlock).
      The ceremony now starts at the document edit, which is its only account-log
      read; the read-only retirement gate (preflightUnlockCredentialRetirement)
      is unchanged.
    • forgetLastEnrolledClient's unlockMethods option, the
      UnlockMethodsRemintReach type, RecordRemintFailedError, and the
      unlockMethods member of LastEnrolledClientForgetResult (/clientAnnex).
      The onBeforeRemoval seam, which re-binds the LOGIN credential's own
      record, is unchanged and is now stage 5 of 6.
  • removeUnlockKey's expectedLadderVmIds option and
    LadderInventoryDriftError (/unlock): the cross-check tied the edit's
    ladder attribution to a pre-edit read only the removed re-mint stage made.
  • revokeAccountClient's ownSigningKeyMultibase option (/clients). Its only
    effect was the wording of the self-revocation refusal, which the document edit
    enforces from the signer's own seeds regardless; an app that omitted it saw
    different prose for the same refusal. The edit's refusal ("a client cannot
    revoke itself") is now the one wording, and the self eligibility refusal in
    clients/policy.ts remains the surface's rule.

Added

  • recoverySpendRetirementFromLog and RecoverySpendRetirement (/recovery):
    the retirement report of a recovery spend whose add-and-retire entry already
    stands, read back off the log from the successor's public halves --
    retiredCredentialVmIds, struckRungHashes, unclaimedCredentialVmIds, the
    same members both continuations return. The continuations' completed branch
    now calls it, so a resume that never re-enters the continuation (freewallet's
    remembered spend resume) consumes one definition of what the spend retired
    rather than a document-membership test of its own. A log that does not
    authorize the successor key reports nothing retired.

  • walletSpaceProvisioner and WalletSpaceProvisioningError (/keys): builds
    the sync engine's ensureProvisioned closure for a wallet Space --
    provisionWalletSpace then ensureWalletSpaceEpochs, single-flight across
    concurrent callers, throwing when a collection was left without its first key
    epoch so the engine never memoizes a torn run. An optional onSettled hook
    receives every run's epoch report, a partial one included, ahead of that
    refusal, so an eager minter still gets the descriptors that did settle.

  • collectionDescriptorLogPinId (/descriptors): the pin-slot key for a
    collection's governing descriptor log,
    space/<spaceId>/key-map/<collectionId>.jsonl.

  • isAppConnectQuery / isWalletOnboardingQuery (/request), beside
    isZcapQuery: the one place each query type's type string is matched.

  • advanceLogPin (/webvh), concludeUnchangedAccountEntry (/webvh),
    isSoleEnrolledClient and ladderVmSigners (/clientAnnex): the shared
    forms of four blocks the ceremonies used to restate per site.

  • RecoveryCredentialStandingError (/recovery): the recovery continuations
    refuse, before their reveal entry, a fresh credential whose keyAgreement id
    already stands in the document (the same passphrase re-typed). The entry
    retires every pre-recovery credential, and one it re-bound instead would keep
    its old rung commitments under a struck ladder VM. The error carries the
    standing ids; nothing is published.

  • AccountEntrySigner (/webvh): signAccountEntry signs under a third,
    seam-only arm, { kind: 'committed', updateSeed } -- a bare update key the
    published log commits or already authorizes, revealing itself with the ladder
    arm's unions. AccountLogSigner, what a ceremony body accepts, is unchanged.

  • introducedCredentialKeys / retiredCredentialKeys
    (resourceLog/document.ts, surfaced through /webvh): the credential-class
    keyAgreement ids one entry introduces and retires, the one definition the
    anchor rule and the resumed spend's report share.

  • mergeVerificationMethods and RelationMembership (/webvh): the one merge
    of added verification methods into a published document across the five
    relations -- same-id replace into verificationMethod, per-relation dedupe,
    an optional retirement predicate over the existing document only. The
    enrollment add entry, the self-enrollment add entry, and the recovery
    add-and-retire entry build through it instead of three inline copies.

Fixed

  • Eight catch sites that classify a rejection on its name read it through an
    optional chain (clientAnnex/heal.ts, clientAnnex/rosterDeliveredEpochs.ts,
    clientAnnex/forgetLast.ts, clientAnnex/log.ts, keys/spaceEpochs.ts,
    webvh/revokeClient.ts). A seam rejecting with a nullish reason -- an
    app-injected store's bare Promise.reject() -- now propagates as it is
    instead of raising a TypeError from inside the catch. Every site's other
    branch rethrows, so no nullish rejection is adopted into a re-run or a skip.
  • isDIDAuthOnlyRequest (/request) reads the query set through queriesOf,
    as classification does, so a body carrying a null or untyped query entry
    beside its DIDAuthentication query is DID-Auth-only to both predicates
    rather than to one.
  • The credential-anchored establishment's stage-3 pointer entry no longer fails
    on a lost race that retired its signing rung. The entry used to retry with a
    pair fixed before the retry, so a sibling self-enrollment spending rung 0
    between the read and the PUT ended the run on an untyped not-authorized error
    after the annex Space and generation were minted (the stage-3 orphan residue).
    The entry is now moved as the ladder: every attempt attributes the ladder's
    current rung, reveals it when only its hash stands committed, and signs with
    it, landing the pointer on the winner's head with no second mint. The registry
    records the rung the entry was signed with, or, when the document already
    pointed, the ladder's currently attributed rung. The stage-3 primitive
    attributes the rung before anything is minted, so a ladder the account log no
    longer anchors refuses with no annex Space or generation minted.
  • A second recovery now retires the credentials the first recovery introduced.
    The recovery add-and-retire entry is read as a bind shape by the anchor rule
    (credentialLadderAnchor, /clientAnnex): the fresh credential anchors on
    the successor key the entry authorized, and the replacement code on the reveal
    entry's last committed hash, paired by the keyAgreement relation's order
    (decisions/0014, amended). A later recovery therefore strikes the first
    recovery's passphrase rung (standing authorized in updateKeys until now) and
    its replacement code's commitment, and reports neither on
    unclaimedCredentialVmIds; a client removal's derived latent-hash set claims
    a recovery replacement's commitment without the caller vouching for it. The
    two anchors are independent: a replacement lookup that refuses leaves the
    fresh credential anchored. A transient continuation torn at its seam more than
    once (a fresh ladder seed each time, the same replacement) still anchors both,
    the replacement's hash read past every resumed reveal to the one attempt that
    committed it. A remembered continuation resumed with a different replacement
    code than its reveal entry committed is refused an anchor rather than guessed;
    a transient one anchors the replacement the document carries, and refuses only
    when two attempts committed different ones.

Changed

  • The "install collection epochs only under the key the user-key roster
    delivers" gate is one place: ensureWalletSpaceEpochs (/keys) takes an
    optional rosterDescriptor and refuses the whole fan-out when the roster's
    current epoch is not the given user key, reporting it on the result's new
    skipped member ({ rosterEpochId? }, nothing written). Both genesis
    ceremonies hand it their landed roster: ensureAccountGenesis (/genesis)
    now sets AccountGenesisResult.epochsSkipped too, so a re-run adopting a
    roster keyed to another user key no longer installs epoch[0] under the key it
    was handed, and it runs no epoch stage at all behind a failed roster stage (no
    roster means no epochs; the re-run installs both).

  • ensureRosterDeliveredEpochs's beforeMint seam (/clientAnnex) is
    required, refused with a TypeError before any read when absent: a served
    absent roster is the one observation that can turn the stage into a
    single-recipient roster genesis, so every caller states what licenses the
    mint. The credential-anchored establishment's adopted-roster arm, which
    supplied no preconditions, now refuses the mint outright, since the genesis
    adopted a present roster a moment earlier and a host serving it absent
    afterwards is contradicting itself.

  • mintClientWebvhUpdateKeys (/webvh) is synchronous: it only draws two
    random seeds, so it returns the pair directly instead of a promise. Callers
    drop the await.

  • appConnectRequestOf (/request) and walletOnboardingRequestOf
    (/request/onboarding) share singletonQueryOf
    (request/queryPredicates.ts) for the one-mental-model-per-exchange check --
    exactly one query of the type, none of the mutually exclusive types -- so a
    third singleton query type is one call rather than a restated loop.

  • The recipient-retiring roster paths acquire the roster once per write.
    rotateUserKeyRoster and replaceUserKeyRosterRecipients (/keys) take an
    optional current -- the read the caller just performed on the same store
    instance (DescriptorStoreRead) -- and seed was-client's compare-and-swap
    from it instead of reading again; a lost compare-and-swap re-reads as before.
    convergeUserKeyRosterToDocument seeds every branch from its own read (or
    from a threaded descriptor when its etag comes along),
    retireRosterRecipientAndCascade seeds its rotation from the read it decided
    on, and addUserKeyRosterRecipient seeds its escrow the same way.
    UserKeyRosterReadResult carries the etag of a read it fetched itself, and
    convergeUserKeyRosterToAccount (/clients) takes it as etag so the login
    sweep's convergence write is seeded from the start's read; the sweep then
    adopts a rotation off the rotation's own result rather than re-reading. On the
    log-governed store that is one hash-chain walk fewer per client disconnect,
    credential retirement, and forget, and two fewer per converging login sweep.

  • The log-governed roster store's replace no longer refuses when no read on
    the same instance precedes it: it acquires the verified head itself and
    appends under the caller's validator, so a compare-and-swap seeded from
    another instance's read costs one read rather than a refusal. A replace on an
    absent log is still refused.

  • preflightUnlockCredentialRetirement (/unlock) also returns the attributed
    ladderVmIds and the document the gate ran over; retireUnlockCredential's
    stage 0 now calls it instead of restating the read-attribute-gate sequence.

  • verifyAccountLog's substituted-account refusal shares
    assertPublishedLogDid with the ceremony reads, so its message is now the
    shared one (resolves to a different DID (X) than expected (Y)).

  • deleteUnlockSpaceWithCapability (/keyring) is a re-export of
    deleteSpaceWithCapability rather than a wrapper.

  • Independent awaits run concurrently: the unlock record's member seals and
    opens, the recovery spend's hash derivations, the contacts LWW decrypts, the
    mend's collection describes, and the last-client forget's delegation
    revocations.

  • The two recovery continuations (recoverWebvhClient,
    recoverWebvhLadderAnchored) run over one shared body,
    recoveryContinuationOnce in recovery/continuation.ts; each variant
    supplies only its successor key, the methods and relation memberships its
    entry adds, and what its onCommitted seam hands back. Both entries go
    through signAccountEntry on its committed-key arm, so the pinned read, the
    carry-over precondition, the conditional publish, and the pin advance are the
    seam's; the add-and-retire entry is built on the head the reveal entry's own
    publish leaves standing, with a re-read under the same pin only against a
    store whose PUT serves no ETag. The entries both publish are byte-identical to
    before. RecoveryKeyNotCommittedError, recoveryVmId, and the recovery key
    types now live there, re-exported from recoveryWebvh.ts, so the /recovery
    subpath is unchanged.

  • SyncEngine (/sync) memoizes ensureProvisioned: once a call resolves,
    later cycles skip it until the new invalidateProvisioning() is called; a
    call that throws is not memoized. A new optional remintPending dep runs
    every cycle right after provisioning, before the migration sweep and the push,
    so an eager minter's create-loss re-mint always lands ahead of the push.

  • Breaking. delegatedWebvhLogStore (/webvh) no longer takes
    publicRead. The read mode is now decided from the wallet Space roster: the
    world-readable id collection is fetched unauthenticated, and every other
    collection (an annex generation included) reads through the delegation.

  • Breaking. logGovernedDescriptorSource (/descriptors) takes spaceId
    in place of logIdFor; it keys each collection's chain-head pin itself with
    the new collectionDescriptorLogPinId, so no caller builds the slot.

  • clientRemovalFields (/webvh) now derives the latent commitments a
    staged-hash attribution must exclude from the log itself
    (standingCredentialLatentHashes, walking every standing credential's
    ladder), cross-checking the caller's knownLatentHashes against that
    derivation and logging a mismatch rather than trusting either list blind. The
    derived set never removes the hash the decision-0007 position names as the
    client's staged hash.

  • revealLadderRungWebvh (/clientAnnex) now returns the rung it attributed
    and the head it leaves standing ({ revealed, rung, published }), assembled
    from the entry's own updateDID result and publish ETag. The transient
    readiness pass's pointer move builds its pointer entry on that head with the
    rung in hand, dropping one did.jsonl read and one ladder attribution per
    attempt. The LadderSignedEntryOutcome type carries the publish's etag, and
    the new accountEntryHead (/webvh) is the one head assembly over the
    account-entry seam's outcome, shared with the pointer entry. A head with no
    validator is still re-read and re-attributed, so the pointer entry's
    compare-and-swap never degrades to an unconditional write. The
    self-enrollment's add entry builds on its reveal entry's head the same way,
    with the same no-validator fallback.

  • delegatedWebvhLogStore (/webvh) now hands back the delegated PUT's ETag,
    as the root id-collection store already did. Without it every
    bridge-delegated publish left the next entry to re-read the log for its
    compare-and-swap token.

  • The ladder-held #DelegatedClients pointer move is one account-log entry.
    setDelegatedClientsPointer (/clientAnnex) signs on the ladder arm as well
    as the client arm: the attributed rung reveals itself in the pointer entry
    and, when it stood only committed, the entry commits the next rung's hash
    beside it, where a reveal entry followed by a client-signed pointer entry used
    to land. movePointerAsLadder (now exported) is that arm over the transient
    readiness pass's and the establishment's pointer moves, and the outcome
    carries rung on the ladder arm. pointerEntryUpdateKeys and its fallback
    re-read are gone with the second entry.

  • ensurePointedClientAnnexGeneration (/clientAnnex) takes an explicit
    signer: { kind: 'client', updateKeys } for an enrolled client's own update
    keys, or { kind: 'ladder' } to move the pointer as the ladder under the
    supplied ladderSeed, which is what the credential-anchored establishment's
    stage 3 does. The outcome's rung is the ladder arm's. The updateKeys
    member is no longer accepted.

  • signAccountEntry (/webvh) and setDelegatedClientsPointer default
    logOnly per arm (true on the ladder arm, false on the client arm)
    instead of forcing it on the ladder arm, so a ladder-signed entry written
    through a root-invoking store can republish the did:web projection beside
    itself by passing false.

  • withThreadedHeadOnce moved to /webvh, beside withLogConflictRetry, so a
    base ceremony that saved a read can use it.

  • mintedNewClient joins the test/node/fixtures/clientKeys.ts fixture, in
    place of seven file-local copies.

  • issueAppKeyCredential (/request): the caller-supplied-seed app-key
    credential issuer under mintAppKeyCredential and reissueAppKeyCredential
    is now exported, so an application's own self-issue path signs the same
    credential shape instead of maintaining a copy. It takes an optional
    documentLoader (defaulting to the shared security loader) and an optional
    description (defaulting to the wallet's consent sentence).

  • retireRosterRecipientAndCascade (/keys): the recipient-naming twin of
    rotateRosterToDocumentAndCascade, for a ceremony that rotates the roster
    before its own document edit. It retires one named roster recipient, reads the
    fresh key back through a caller-supplied key-agreement key, and runs the same
    collection fan-out, with no seal backstop.

  • anchorRosterStoreAt (/keys): sets a log-governed roster store's minimum
    controller version from a given account log; shared by both cascade entry
    points.

  • rosterWrapsRecipient (/keys): the shared "does the current epoch wrap to
    this recipient" probe, now also used by addUserKeyRosterRecipient.

  • UserKeyAdoptedHook (/keys): the named type for the roster-adoption
    persistence callback both cascade entry points take.

Fixed

  • The enrollment wrap (addUserKeyRosterRecipient, /keys) now refuses a
    roster whose currentEpoch names no epoch in its own list, instead of
    proceeding to addRecipient. The collection cascade's per-collection op
    raises UserKeyRosterIntegrityError for the same shape rather than a bare
    Error. The new currentEpochOf (/keys) is the one implementation both
    sites, and the roster read, now share.

Changed

  • The seeded ladder-rung scans (/clientAnnex, attributeLadderRung and
    attributeLadderInventory) derive the scanned range concurrently rather than
    one rung per await. Each rung is an HKDF expansion plus an Ed25519 keygen,
    and the default range is 128 rungs. The full range is still scanned, the
    classification order is unchanged, and no refusal changes.
  • The client-annex generation minters (/clientAnnex,
    mintClientAnnexGeneration and mintCredentialClientAnnexGeneration) return
    the published head of the genesis log they just wrote -- the log, the DID and
    document, the effective update-key parameters, and the PUT's own ETag when the
    store hands one back -- rather than the log and document alone.
  • The credential-anchored establishment's generation-delegation install stands
    on that head instead of re-fetching and re-resolving the log the mint wrote a
    moment earlier: one fewer serialized request on every signup. The GC
    generation swap and the annex heal's fresh-generation arm do the same.
  • A head carrying no ETag is not carried forward, since the install's entry
    publishes under a compare-and-swap and a head with no validator would degrade
    that to an unconditional write. The install then reads for itself, as before.
    Either way its own publish advances the generation's chain-head pin.
  • The fresh-generation block the credential-anchored paths share -- mint the
    generation, install its generation delegation, append the account document's
    pointer entry -- is one helper, mintPointedClientAnnexGeneration
    (/clientAnnex), with the pointer write injected: the establishment's two
    arms sign it as a client, the transient visit's fresh-generation arm moves it
    as the ladder. The establishment's bootstrap arm runs its controller flip
    through the helper's beforePointerEntry seam, between the install and the
    pointer entry, as before.
  • rebindCredentialAnchoredRecord and credentialAnchoredStandingFields
    (/clientAnnex) are the one builder of the ladder-VM-signed bridge, sibling,
    and record re-bind, and the one builder of the standing fields a registry
    entry records; the establishment's stage 4 and the mend's record-downgrade
    re-bind and registry arm now share them.
  • The annex heal's two pre-flight attributions merge into
    attributePointerEntryRung (/clientAnnex), the one place a
    LadderAttributionError maps onto ClientAnnexGenerationUnavailableError;
    pointerEntryUpdateKeys is a wrapper over it that still refuses a
    committed-only rung.
  • The controller flip's error-handling asymmetry is now stated in code: the
    establishment's bootstrap arm swallows an authorization-class refusal because
    its Space may be one a concurrent run already flipped, while the annex heal's
    fresh-Space flip acts on a Space id minted a moment ago that no other run can
    hold, so it propagates every failure.
  • forgetEnrolledClient and forgetLastEnrolledClient (/clientAnnex) now run
    their roster rotation and collection fan-out through
    retireRosterRecipientAndCascade instead of restating it, so both forgets set
    the roster store's minimum controller version the same way the revocation
    cascade does.
  • BREAKING: forgetLastEnrolledClient takes a
    rosterStore: EncryptionDescriptorStore option instead of a
    rosterStoreFor: ({ did, log }) => store factory. The ceremony now anchors
    the roster store's minimum controller version itself -- at the pre-transition
    head for the opening read, at the post-reinstall head for the rotation -- so a
    store wired over a cached pre-transition view still anchors the transition's
    one ladder-signed roster append past the reinstall entry. Callers building a
    rosterStoreFor factory must pass one store instead. The store must be
    anchorable (SealableEncryptionDescriptorStore); one without
    setMinimumControllerVersion is refused with a TypeError before any read,
    since the strike-and-reinstall pair would otherwise publish two entries per
    attempt ahead of a rotation the ceremony-tail license refuses every time.
  • isSealableDescriptorStore (/keys) probes both interface members (seal
    and setMinimumControllerVersion) rather than seal alone.

Removed

  • removeUnlockKey's requireLadderVmClaim option (/unlock): every caller
    passed true, so the retirement gate now runs unconditionally on the remove
    polarity.
  • ladderSigningPair (/clientAnnex): the client-arm pair of a revealed rung
    has no caller now that the pointer move signs as the ladder.
  • The enrolled-client listing (listEnrolledWebvhClients, /webvh) attributes
    every client's active update key in one forward pass over the log, matching
    its single-pass enrollment-index scan, instead of walking the log tail once
    per client. The pass tracks only the clients the final document lists, starts
    at the earliest of their enrollment entries, and builds no per-entry Set.
    attributeClientUpdateKey runs the same pass for one client, which is the
    tail walk from that client's add entry.