A three-source conformance checker for AT Protocol Personal Data Servers (spec + lexicons + reference behavior). Pre-alpha.

Lifecycle checks and provisioner teardown #7

Closed katsuricata.com
Closed opened by and assigned to katsuricata.com

The consent ladder has a lifecycle mode that no check uses yet. Two pieces:

  1. Provisioner teardown: deactivate/reactivate/delete support in src/provision.ts so lifecycle checks can drive account state transitions.
  2. Lifecycle checks: the account-lifecycle guide gives exact expectations: #identity → #account → #commit ordering on creation (already covered), no #commit events for non-active accounts, an empty #commit on reactivation after long deletion, and migration end-state invariants (DID doc + active status + valid repo with higher rev). Per the spec these are should-level (the migration flow is explicitly non-normative; see docs/spec-gaps.md).

This is the highest-signal remaining tier: it catches the "relay silently stopped picking up my PDS" class of bug.

Done when: --mode lifecycle runs checks that exercise deactivation/reactivation against the oracle and assert the firehose event sequences.

Done in b46270a (main).

What shipped:

  • Provisioner teardown: AccountSession now carries deactivate()/activate()/delete() (src/context.ts; every provisioner-created session gets them, delete() sends the provisioner-known password). New can-delete-account capability so deletion checks skip cleanly on targets without deleteAccount.
  • New src/checks/lifecycle.ts, four lifecycle-mode checks, all run against the oracle in pnpm e2e (default mode lifecycle):
    • sync.lifecycle.reactivation-sync -- reactivation emits #account then #sync, #sync rev == repo head, commit-only blocks.
    • sync.lifecycle.deactivated-redistribution -- public getRepo degrades (reference serves an empty repo; 400 also acceptable), owner export preserved, getRepoStatus/listRepos mark the account deactivated, no #commit after the deactivation frame.
    • sync.lifecycle.deletion-events -- deleteAccount token gate (reference: 400 InvalidToken without the emailed token), tombstone #account and backfill purge asserted when deletion is observable.
    • sync.lifecycle.migration-end-state -- DID credentials consistency (getRecommendedDidCredentials vs handle/service endpoint), active status, public getRepo verifies against the advertised signing key with rev == getRepoStatus rev.
  • sync.lifecycle.deactivation-events now routes through the session helpers and declares can-delete-account for its delete probe.

Two findings worth recording (now in docs/interop-traps.md):

  1. The reference does NOT sign a fresh commit on reactivation. activateAccount re-sequences the unchanged head commit into the #sync, so rev is identical before and after a deactivate/reactivate cycle. The guide's "empty #commit after reactivation" (which implies higher rev) is therefore asserted as should, paired with a divergence for the reference behavior. A rev regression satisfies neither and is always wrong.
  2. The reference's public getRepo on a deactivated account returns 200 with an EMPTY repo (zero records), not a 400 -- the availability gate passes optional-auth callers through and the store reads as empty. Owner and admin still get the full export (the migration path depends on it).

Also fixed along the way: --only prefixes were being swallowed by flag parsing in cli.ts/e2e.ts, so per-check reruns silently ran nothing.

Gate: pnpm typecheck clean; pnpm e2e green (exit 0) -- 47 lexicon pass, 13 spec pass, 2 reference pass, 0 fail. The one remaining warn is the intentional spec-side should (fresh-commit-on-reactivation), which the reference itself does not satisfy.

Sign up or Sign in to add to the discussion.