Before you upgrade
Do these while 0.9.3 is still installed:- Stop every 0.9.3 process that uses the state directory: gateways,
SDK-hosted runtimes and maintenance tools. The migration refuses a
directory on which a process still hosts runtimes (
RuntimeInUse). A directory last used by Meta 0.9.2 migrates the same way, with no 0.9.3 start in between: 0.9.4 upgrades its mob store itself. Only the copy from step 6 takes it back to 0.9.2: the--applybackups do not hold the mob store, and any 0.9.4 start upgrades it. So does a published 0.9.3 started on the migrated directory by mistake: it takes the same mob store step 0.9.4’s first start takes, then refuses the convertedruntime.sqlite. After either, 0.9.2 refuses the directory (schema for domain `mob` is from the future). - If 0.9.3 ever ran with
store_pathnaming a file, runstorage-migrate --session-dbbefore anything else, with every process that uses the directory stopped (0.9.3 gateways started with thatstore_pathincluded). See A session database named by store_path. - If your deployment passed a continuity provider to the SDK builder
(
continuity_storein Python,continuityStorein TypeScript), export its continuity now, with 0.9.3 still installed, as Exporting SDK-hosted continuity from 0.9.3 describes. 0.9.4 has no runtime for that provider. - Run any
meta_repairtranscript repair you still need. 0.9.4’smeta_repairkeeps only--composition-manifests. See Transcript repair. - Check every identity whose spec sets a
placement: its placement host must run with a persistent (SQLite) realm backend. Otherwise Meta 0.9.4 refuses to create the identity there (HostRejectedBuild, naming this remedy). Remove the placement to host the identity in-process instead. - Take a copy of the whole stopped state directory, and of the mob
store file with its
-walfile when an embedded app keeps it outside the directory (the file you pass as--mob-store, see Embedded builder apps). The migration is one-way, and this copy is the way back to 0.9.3 (see Going back to 0.9.3). Rehearsing the migration on a clone of the directory first is a good idea; on another machine, or as another user, copy the comms identity keys with it (see Session comms identity keys).
Continuity stores and lease providers are removed
You see:rpc_gateway answers meta/init with invalid params (-32602):
persistent_state directory are the only copy, and
an identity belongs to the one runtime that holds the directory (see
One runtime per state directory).
Removed from both SDKs (see the CHANGELOG for every name):
- the builder options
continuity_store,lease_providerandscratch_dir(TypeScriptcontinuityStore(),leaseProvider()andscratchDir()), the provider protocols and their record and lease types; - the lease-lost error (
LEASE_LOST_CODE,LeaseLostError); request_continuity_repair()and themeta/request_continuity_repairmethod, which now answers method not found (-32601);- the
lease,continuity_healthandcheckpoint_versionfields of the identity status; - the session store argument of
session_service()and the session store config helpers: Core’s session store holds every session.
-
A deployment without a continuity provider: remove nothing but the calls
above, set
persistent_statefor durable sessions, and migrate the state directory as the next section describes. -
A deployment with a continuity provider: export its continuity with
0.9.3 still installed
(Exporting SDK-hosted continuity from 0.9.3),
upgrade, then import the file with
meta_gateway storage-migrate-native --continuity-export <file>into the 0.9.4 state directory:- a new directory (absent, or holding no store file): the migration creates the native stores and imports every exported session;
- the host’s own
persistent_statedirectory, when it holdsruntime.sqliteand no local session projection: the export takes the projection’s place, and schedules, the work graph, the console, blobs, jobs and the peer key stay where they are.
runtime.sqlite, or a store 0.9.4 already uses is refused (ContinuityExportDirectory), and so is a file that is not a valid export (ContinuityExportUnreadable). A rerun that finishes an unfinished run needs the export file that run started with, at the same path: another file, or the same file at another path, is refused (ContinuityExportChanged, naming the path the run started with). -
0.9.3 hosts built with the Python or TypeScript SDK never saved session
snapshots. Their export names the missing snapshots, and
--missing-snapshot freshexports those identities so that their first 0.9.4 wake starts fresh with an empty transcript, as every 0.9.3 restart of such a host did. The export guide explains it.
Migrate the state directory
Every 0.9.3 state directory (thepersistent_state of rpc_gateway and the
SDK builders, or the store directory of meta_gateway) is migrated once,
offline, with 0.9.4’s meta_gateway:
continuity.db, sessions.db,
identity_continuity.sqlite, sessions.sqlite) is migrated and retired in
place under that name, as the 0.9.4 layout resolves it; the steps below that
name continuity.sqlite3 or sessions.sqlite3 mean that file. Run
meta_gateway storage-migrate --state-dir <dir> --apply first when two
spellings of one database sit side by side, and add --session-db <file>
when 0.9.3 ran with store_path naming a file (see
A session database named by store_path).
The dry run changes no database in the state directory. It copies
runtime.sqlite and the session projection databases, each with its -wal
file, into a new scratch directory (in the state directory’s parent, or
under --scratch-dir <dir>), classifies every session by comparing Core’s
runtime store with Meta’s session projection, checks every identity against
Core’s mob store, and prints a report (--json for machine-readable output,
which spells the names below in snake_case). The scratch directory is
removed when the run ends. A read-only open of a database
in WAL mode may leave empty -wal and -shm files beside it, and the
maintenance fence empty *.mpresence lock files beside the databases;
nothing else changes. Exit code 0 means clean, 1 means at least one
refusal, 2 a usage error.
--apply refuses while the census refuses any row, so a refused directory
stays usable by 0.9.3. Otherwise it writes
storage-migrate-native.journal.json into the state directory before its first
write, keeps backups (see Going back to 0.9.3),
converts runtime.sqlite in place onto Core’s head-canonical profile,
imports the sessions only the projection holds into Core’s session store,
writes the first-boot manifest (see The first boot),
retires the old projection, and removes the journal after its last step. It
retires sessions.sqlite3 by replacing it with a retired stamp, and
continuity.sqlite3 by emptying its session tables; the identity records in
continuity.sqlite3 stay. The conversion and the import move each image a
session holds inline, its compacted history included, to the state
directory’s blob store (blobs/) and sync it there before they write the
row that names it; the census row says how many images move. A Rust host
whose runtime reads images from its own blob store (one passed to
UnifiedRuntimeBuilder::blob_store() or binary_blob_store()) copies
blobs/ into that store before its first 0.9.4 start; otherwise every turn
of a session whose context names a moved image fails (“required image blob
is unavailable”).
A run that stops part way (a crash, a kill, a power loss, or a step that
fails, which --apply reports as ApplyFailed) leaves the journal, and 0.9.4
refuses to start while it exists (MigrationIncomplete); a dry run refuses
the same way. Run --apply again. Once Core had written its claim to
runtime.sqlite (it does so before it converts anything), the rerun finishes
from the journal and keeps its backups, and the report says it finished an
interrupted run; before that, the rerun drops the journal and starts over with
a new census and new backups (the stopped run’s backups stay), and the report
says it started over. The journal’s done list records start_conversion,
durably, before the run’s first write to runtime.sqlite; without it, nothing
was written there. With it but without Core’s claim, the run can have written
at most the session store’s schema there (0.9.3 ignores it) and discarded the
runtimes --discard-bodyless named. A session already imported is compared
with its source and kept; a different one refuses the run, and nothing is
written. A journal that cannot be written refuses the run before anything else
is written (ApplyNotStarted). After a completed run the directory reads as
native, and the verb reports that there is nothing to migrate.
Disk and time. The census copies runtime.sqlite and the session
projection databases (with their -wal files) into its scratch directory
and deletes it when the run ends, so it needs free space there (the state
directory’s parent, or --scratch-dir) of about their size. --apply keeps
its backups in the state directory: about the size of the retired
projection databases plus runtime.sqlite (a run that starts over adds
another set), until you delete them with
meta_gateway storage-prune --state-dir <dir> --apply (a run without
--apply lists what it would delete; it deletes backups at least 30 days
old, and --older-than-days 0 all of them). The time is dominated by
copying and by validating each session’s transcript, roughly linear in
transcript volume. The migration uses no network.
What carries over:
- every session the census admits, with its transcript as Core holds it;
- every identity’s current binding. History beyond the current bindings is not migrated;
- rewrite revisions: Core keeps the newest four rewrite revisions of a
session’s transcript, and revisions past that retention are not carried.
read_transcript_revisionon such a revision answersTranscriptRevisionRetiredafter the upgrade. No 0.9.3 RPC, HTTP, console, SDK or CLI surface served them.
Sessions the census reads as equal, as behind Core’s copy, or as held by
runtime.sqlite alone are admitted. A session only the projection holds,
with no trace in runtime.sqlite, is admitted when its copy loads and its
history validates: --apply imports it into Core’s session store, as 0.9.3
re-mints such a session on its next activation. Projection rows past Core’s
copy are admitted in two cases, and --apply then keeps Core’s copy and
drops those rows (the pre-apply backup of the projection database keeps
them):
ProjectionSuffixTerminal, in any directory: the session has a lifecycle terminal fact, so 0.9.3 drops those rows itself;ProjectionSuffixUnbound, only in a directory whose projection iscontinuity.sqlite3: no continuity record binds the session, so 0.9.3 can never write it and never serves those rows.
Bound), and, with in-memory mob storage where
Core holds no binding, every bound identity whose session no other record
binds (Resume), gets a row that
resumes its session. An identity 0.9.3 deleted (Core’s last binding is
retired, and the projection holds no session for it) gets a row that starts
it fresh (FreshAfterDelete).
Embedded builder apps
An app built with the SDK builder andpersistent_state keeps its sessions
and identities in that directory, and migrates like a gateway’s directory.
- Since 0.9.4,
persistent_statekeeps durable mob storage at<state>/event_log.sqlite3by default. In-memory mob storage together with a roster underpersistent_stateis refused when the runtime is built. - So a definition edit between restarts (for example a profile’s model or a
skill) now refuses with
-32019divergent, as on the gateways. Declare the change withruntime_options.declare_spec_update({expected_revision}; the SDK builders’declare_spec_updateanddeclareSpecUpdate, orspec_update_ceremony::declare_spec_updatefrom Rust), or launch the candidate withruntime_options.mob_composition.candidate_definition = "stored". - If your app kept its durable mob store anywhere other than
<state>/event_log.sqlite3, pass it to the migration with--mob-store <path>. The report prints the mob storage it read. The census refuses:MobStoreUnnamedwithout the flag, when the state directory names a mob store other thanevent_log.sqlite3;MobStoreMissingwhen the file--mob-storenames does not exist;MobStoreConflictwhen the directory has its ownevent_log.sqlite3and--mob-storenames another file;MobStoreManifestWithoutStorewhen the directory holds the composition manifest 0.9.3 writes beside its ownevent_log.sqlite3, but not the store, and no--mob-storenames one: the store was moved or deleted. Restore it, or name it with--mob-store <path>.
- The builder no longer has
session_store().ephemeral_runtime_store(true)together withpersistent_stateis refused asConflictingConfiguration. rpc_gatewaywithpersistent_staterefusesruntime_options.runtime_store = memory, and refusesruntime_options.mob_storage = memorywhen a roster provider is registered (both-32014). So a roster gateway that kept mob storage in memory (runtime_options.mob_storage = {"storage": "memory"}) to editmob_configfreely is now refused at start: drop that declaration and declare each definition edit withdeclare_spec_update.
A session database named by store_path
If 0.9.3’smeta_gateway ran with an init store_path that names a file (a
path with an extension, such as <dir>/my-sessions.db), that file is the
installation’s session database, and its parent directory is the state
directory. 0.9.4 opens no separate session database: sessions live in
runtime.sqlite under the state directory.
You see: meta_gateway refuses meta/init with -32014
(SessionDatabaseOverrideRetired), naming the file and the state directory,
whether or not the file exists. Or startup, or the census, refuses
UnrecognizedSessionDatabase naming a file in the state directory.
Do: if 0.9.3 ever ran with store_path naming a file, stop every
process using the state directory (0.9.3 gateways started with that
store_path included), then run storage-migrate --session-db before
anything else:
store_path.
- The file must sit in the state directory, as 0.9.3 required. It moves to
<state>/sessions.sqlite3, and its old path keeps a retired stamp, so a 0.9.3 gateway restarted with the oldstore_pathrefuses instead of starting with an empty session database. - If
sessions.sqlite3already exists beside it, a byte-identical copy is merged and a different one is refused: the refusal shows the command with--adopt <file>, naming the file 0.9.3 used while the override was set. - A file that is not a 0.9.3 session database is refused before anything is
moved, and so is a run while a process still holds the directory’s
runtime locks (
RuntimeInUse). The dry run checks the same things. A rerun after the move reports that the file already moved. - After 0.9.4 has started on the directory,
--session-dbrefuses (NativeRuntimeAlreadyStarted) with nothing moved, and the file’s 0.9.3 sessions stay in that file, untouched: they cannot be brought into a directory 0.9.4 already uses. Move the file out of the state directory; the next 0.9.4 start refusesUnrecognizedSessionDatabaseuntil you do. - A backup you keep in the state directory under another name is refused the same way. Move it out of the directory.
The first boot
When the census plans first-boot rows (any directory whose identities a roster bound),--apply writes
<state_dir>/storage-migrate-native.first-boot.json and a byte copy of it
beside the backups
(storage-migrate-native.first-boot.json.pre-0.9.4-<timestamp>.native-migration,
named when the journal starts, so a rerun rewrites the same copy).
The first 0.9.4 boot finishes the migration from it: for each identity it
either resumes the identity’s exact migrated session or starts it fresh,
where 0.9.3 had deleted it (or where meta_gateway identity-reset asked for
a fresh life, see Offline reset); a resume row whose
session Core already binds reads as done. rpc_gateway with persistent_state, meta_gateway with persistent
sessions and the SDK builder all carry the manifest out; meta_gateway’s
ephemeral launch has no state directory to read. meta_gateway storage-prune keeps the copy, however old, while the manifest is still in
the directory; after that the copy ages out like the other backups.
- An identity the roster lists is carried out when the roster compiles, before any wake of it. An identity the roster does not list stays pending, and is carried out first when it is added back. The file is rewritten as rows are carried out and removed after the last one.
- The bootstrap status (
meta/status_identity_bootstrapandmeta/wait_identity_bootstrap, the SDKs’identity_bootstrap_status()andwait_identity_bootstrap()) lists the pending rows asfirst_boot_pending; the storage doctor lists them too, each with Core’s verdict from the directory’s mob store (carried out, still to carry out, or contradicted). reset,delete_identityand the delivery verbs of an identity whose row is pending are refused (-32014,first_boot_row_pending, retryable). The refusal’sremedysays what to do:add_to_rosterwhen no roster lists the identity (add it, so its row is carried out, then retry), orwake_identitywhen the roster lists it and its row waits to be carried out (wake the identity, then retry). Every Meta entry point that reaches Core’s mob-level reset refuses while any row is pending. A reset Meta does not initiate is not guarded while a row is pending: avoid one until the bootstrap status lists no pending row.- A row that Core contradicts blocks only that identity
(
first_boot_row_refused), not startup. A resume row whose session Core holds no durable copy of is refused the same way. - If the file cannot be read, the roster compile refuses
(
first_boot_manifest_unreadable) and nothing wakes; every delivery is refused the same way, also to an identity whose Core member survived the restart. The gateway stays up for status and the doctor. The refusal names the remedy: move the file aside and keep it. If no 0.9.4 runtime has started on the directory sincestorage-migrate-native --apply, restore the copy that run kept beside it (storage-migrate-native.first-boot.json.pre-0.9.4-<timestamp>.native-migration), then runmeta_gateway identity-resetagain for every reset made since that run: identity-reset keeps no copy, so the restored file lacks them. If a runtime has started since, restore no older copy (its rows were carried out already): start with no manifest, and run identity-reset again for each reset made since the last start (see Offline reset). Removing the manifest also drops a migration row still pending for an identity no roster listed since: that identity starts fresh when it is added back, and nothing reports it, so compare against the lastmeta_gateway identity-censusoutput before removing it. - The first 0.9.4 roster pass respawns each live member of a rostered identity whose Core binding lacks the durable-identity marker (a member 0.9.3 spawned has none) onto its session: the transcript stays, and the generation advances. A member that is not live gets the marker at its next wake. A failed respawn shows as the identity’s typed refusal in the bootstrap status and is retried at every roster pass. A live member labelled for an identity the roster no longer lists is retired.
Starting 0.9.4
One runtime per state directory
A 0.9.4 runtime claims its state directory before it opens any store: it takes an exclusive, non-blocking lock on<state>/meta-runtime.lock and holds it for
the runtime’s life, until a clean shutdown has run to its end or the runtime
is dropped. It also checks that no other process still hosts runtimes there:
it try-locks Core’s <state>/runtime.sqlite.execution-custody (read-only,
never creating it) and releases it at once. A shut-down runtime this process
composed keeps that lock while its handles are still in scope, and a new
runtime in the same process starts anyway. On Linux it reads the kernel’s
lock table (/proc/locks) and is refused when the table names any other
holder of the lock. On macOS and Windows, and on Linux where the table cannot
name the lock’s holders (it cannot be read, or has no entry for the lock
file, as on btrfs subvolumes and on overlayfs such as a container root
without a volume), no other holder can be named, so while the previous
runtime is alive a holder that does not run 0.9.4’s startup check (an older
release, or a Core-only tool on the same directory) is not detected; another
0.9.4 process is still excluded by its own directory claim and check.
A second runtime on the same directory, in another process or the same one,
is refused with STORAGE_RESOLUTION (-32014); a Rust builder caller gets the
typed StorageLayoutError with the same text:
These are kernel locks on open files. They are released when the process
exits, also after a crash or
kill -9: there is no lease or timeout to wait
for and nothing to clean up, and child processes never inherit them. The lock
files stay on disk, and the storage doctor lists meta-runtime.lock as
inventory. Core also keeps one hosting claim per hosted session under
<state>/<realm>/hosting/ and a cold-delivery lock under
<state>/<realm>/delivery/ (<realm> is meta on a fresh install); they
refuse only a second host of the same session, after the checks above.
Startup refusals
0.9.4 checks the state directory before it opens any store, and refuses with STORAGE_RESOLUTION (-32014). Rows marked builder only reach a Rust
UnifiedRuntimeBuilder caller as a build error; no gateway reaches them:
A mob the previous process left stopping. When the previous process
stopped uncleanly part way through its shutdown Stop, Core finishes that Stop
after the resume, and the boot waits up to 60 seconds for it to land before it
resumes the mob to Running. If it does not land in time, the start is refused:
meta/init fails with -32603
(Runtime bootstrap failed: ... mob lifecycle operation made no progress at boot awaiting a Stop an earlier process placed ...;
a Rust builder caller gets MobError::LifecycleOperationProgressStalled with
intent Stop, which Core classes as retryable), and the gateway log keeps
automatic recovery of typed placed lifecycle intent remains incomplete
warnings with Core’s reason. Start the gateway again: the Stop stays durable,
and Core’s recovery drives it again at the next start. If it keeps failing,
the warning names what holds the Stop (usually a member that cannot settle);
fix that first.
An adopted identity after a restart. An identity a host adopted (with
mob/adopt_member_identity_declaration) runs again after a gateway restart
only once the previous process’s identity lease expires, at most 30 seconds
after that process last renewed it. Until then its status’s
convergence.decision reads await_lease, and a send or dispatch to it
answers -32052 (identity_wake_retryable, retryable): retry it, or wait
until the status shows the member live. A scoped dispatch (with
expected_scope) is refused with -32603 (scoped_delivery_rejected) while
Core holds no live member. No Core runtime path releases the lease at
shutdown; a Core fix is planned for a later release.
A runtime store in a storage provider. The RuntimeStoreMissing check
reads only a local runtime.sqlite. A UnifiedRuntimeBuilder composition
whose runtime store is a non-disk storage_provider() backend is checked
against that store instead, once the provider has opened its realm and before
any identity wakes: identities bound to sessions while the store holds no
session at all refuse with RuntimeStoreLostSessions. This happens when the
backend lost its runtime data, or when the store does not outlive the
process. Without this check such a start went ahead: an identity that was live
at the last shutdown came back Active on its lost session, where its turns,
resets and deletes failed with session not found; any other bound identity
started fresh, with no history (its status reports replaced_session).
Restore the backend’s runtime data; or start over on a new, empty state
directory (and a new mob store, if you pass one with
persistent_mob_storage()): the identities then start from the roster on
fresh sessions, and their custody and generation history starts over. An
identity that does come back on a lost
session (see the known limitations for the stores this check cannot read)
recovers when you retire it (IdentityRuntime::retire): its next wake starts
it on a new, empty session. The check reads continuity records in the state
directory and the mob store under it or at a persistent_mob_storage() path;
mob storage without a persistent path and a host-built mob_spec() are not
read yet, see the known limitations.
Back up the state directory as well: a lost or replaced one cannot be
detected.
meta/init_progress no longer reports the prewarm phase:
owner_publication now covers only the early roster callback.
What changes for identities
- View states: an identity is
active,dormantorbroken. The 0.9.3 statesretiring,suspendedanduninitializedare gone. Meta’s 0.9.3brokenmark is not migrated: after the upgrade Core’s history decides an identity’s state, so an identity that 0.9.3 marked broken can come upactive. The status and the inspection carry Core’s facts: the generation, the session and runtime Core binds, Core’s convergence status for an intent-managed identity, the last wake refusal and the session a fresh wake replaced. - Typed refusals: an identity Core cannot resume answers
IdentityUnresumableError(-32051, final); history Core cannot read yet answersIdentityHistoryUnavailableError(-32052, retryable); an identity a host adopted answersIdentityHostAdoptedError(-32053); a wake held until an operator acts answersIdentityWakeHeldError(-32603with a structuredkind). The lease codes-32003and-32005are no longer answered, and-32005is never reused. - Answers:
meta/reconcile_identityreports a refused identity asrefusedwith its error (thebrokenoutcome, the outcomes’generationandcontinuity_repairare gone),meta/retireno longer returns afencing_token, and the respawn and reset results dropcheckpoint_version. Identity lifecycle methods lists the shapes. - A fresh session when Core has none: when Core reports that an
identity’s session no longer exists, the identity wakes on a fresh session
instead of failing, and the status names the session it replaced. A
delivery to a live member whose session Core holds no durable copy of is
refused once (
-32052,identity_session_absent, retryable) with nothing delivered; the next delivery starts the identity’s fresh session first and carries over the inputs other senders had queued on the stuck member (any it cannot carry are named in the log). If that member’s queue cannot be read, the fresh session waits: the delivery answers-32052(retryable) and nothing is replaced. - Respawn, reload, reset and delete: a status read during one of these on
a live member can report
dormantfor a short window, with the predecessor’s session and generation; the next read reportsactiveat the successor’s generation. A respawn advances the generation. - Saved delivery scopes: a scope’s
generationandlease_fencing_tokenare now Core’s member generation and fence token. A scope saved by 0.9.3, captured before a respawn, or captured at another incarnation of the mob (one destroyed and created again under its id) is refused as stale (-32007) and nothing is submitted: capture a new one from the status. - Retention:
delete_identityandresetleave the old session in Core, archived with its transcript: a history read by the old session id still serves it after a restart, and session listings hide it. Read the session id from the status before you delete to keep a handle on it. 0.9.3 deleted Meta’s copy but also left Core’s runtime body on disk, so nothing that 0.9.3 kept is lost. - Extension documents: an identity acts as itself on extension documents
only when Core’s durable-identity marker is on its binding, and a member
Meta 0.9.3 spawned has none. The first 0.9.4 roster pass respawns each
such live member onto its session (see The first boot);
the transcript is kept, and no
meta/respawnis needed. A dormant identity is marked at its next wake. Until then, and for an identity whose respawn failed, extension documents refuse its sessionsAuthorityUnavailable; the refusal’sagent_identitylabel tells it apart from a worker’s refusal. A failed respawn shows as that identity’s typed refusal in the bootstrap status and is retried at every roster pass;meta/respawnstays the manual remedy. The pass leaves some live members as they are, with only a warning in the log. One whose role differs from the roster profile needsmeta/respawn, declaring the role migration. Leave one whose session Core holds no durable copy of running: a respawn would start it fresh and lose the transcript it holds only in memory. Once Core holds its session (after its next turn), the next roster pass respawns it.delete_identityis not the remedy: it starts a fresh session, and the earlier documents stay with the identity’s agent either way. - Held bootstraps: a member that a held bootstrap pass did not publish
(the pass was refused or expired) keeps owing its host a held completion
across a restart. The pass marks each roster identity in
<state>/held-completion-owed/before it creates any member and removes the mark when it publishes the member. After a restart, an ordinary bootstrap or wake refuses a marked member (HeldCompletionOwed) instead of publishing it without the host’s completion, and so does its reset (a delete finds nothing to delete, since Core never spawned the member); a held pass that covers it publishes it. Each mark is a file named by the SHA-256 of its identity and holds the identity. The refusal names the remedy: run the host’s held bootstrap again, or, if the host will never complete it, remove that identity’s mark file. - Role migrations: a profile change of an identity is still gated by its
role-migration declarations; an undeclared change is held
(
identity_role_migration_refused). - Placement hosts: see step 5 of Before you upgrade.
- The console: the roster and the identity details no longer show a
checkpoint version or lease health, and the console JSON API sends
checkpoint_version,continuity_health,lease_healthyandleaseno more. - Reading identity state: a host that read Meta’s continuity database
directly finds its session tables empty after the migration, and its
identity records keep their 0.9.3 values: 0.9.4 never updates them, and
Core’s mob store holds every identity’s binding. Read an identity’s bound
session (
session_id) and its Core mob member id (member_id, the identity’s roster member id: the identity itself when it is already a valid comms name, such asplanner, and itsmk--...encoding otherwise, as for an identity with a:) frommeta/status_identity, or from the records ofmeta/console/list_identities. A deploy-time reader that works on a copy of the directory while no gateway runs reads the same facts offline withmeta_gateway identity-census --state-dir <dir> --json(formatmeerkat-meta.identity-census, version 1): per identity thesession_id,generationandmember_idof Core’s last binding,liveandagent_runtime_idwhile the member is live,runtime_id(the runtime idruntime.sqliteholds the session under),retired,held_completion_owedand thepending_first_boot_row. It writes nothing in the directory: it reads copies of the directory’s top-level files, taken into a temporary directory or under--scratch-dir, which needs room for them,runtime.sqliteincluded. It refuses a directory a runtime holds, and one that is not migrated yet. Exit codes: 0 read, 1 refused, 2 usage error.
Offline reset
meta_gateway identity-reset gives one identity of a stopped state
directory a fresh life at its next start:
- It writes nothing to Core.
--applyadds aFreshrow to the directory’s first-boot manifest naming the session of Core’s last binding of the identity; the dry run, the default, runs every check and writes nothing. The manifest is written atomically (created when none is pending), and no backup of it is kept, so the copystorage-migrate-native --applykept does not hold the rows a reset wrote. If the manifest becomes unreadable, follow the remedy in The first boot: move it aside, restore that copy only if no runtime has started since the migration, and run identity-reset again for the resets made since.--mob-store <path>names the mob store as forstorage-migrate-native. - The next roster compile that lists the identity carries the row out, as The first boot describes: a live member is replaced by its successor on a fresh session and stays live; a retired identity gets a fresh session and stays dormant. The old session stays in Core’s store, bound to nothing. Unlike the online reset, nothing of the old session is distilled into agent memory.
- It refuses, and writes nothing (
refusal.kindin--json):- a directory a runtime holds (
directory, whose nested refusal isruntime_in_use: stop the process first); - a directory the migration has not finished (
not_native: runstorage-migrate-native, or finish its--apply); - in-memory mob storage (
mob_store_in_memory), an identity Core has no binding of since the latest mob reset (unknown_identity), or a binding with no session (no_session, a peer-only member); - an identity that owes its host a held completion
(
held_completion_owed: its held-completion mark is present and Core binds it to nothing yet; run the host’s held bootstrap instead); - an identity a host adopted (
identity_host_adopted: Core holds an identity intent row for it; change it through the host that declared it); - an identity with another pending first-boot row (
pending_row: start the runtime once so that row is carried out, then reset); - the other kinds:
invalid_identity,member_history_unreadable(also an intent row that cannot be read),held_marks_unreadable,manifest_unreadableandwrite_failed;directoryalso nestsnot_a_directory,runtime_lock_unreadable,fence_unavailableand the--mob-storerefusals.
- a directory a runtime holds (
- A held-completion mark beside a Core binding is stale (a held pass
published the member and did not get to remove it): the reset proceeds,
reports
stale_held_completion_mark, and the next start drops the mark. - The same reset already pending is not refused: the run says so and writes nothing.
--jsonprints formatmeerkat-meta.identity-reset, version 1, with theretired_session, itsbinding_state(liveorretired),written,already_pending,stale_held_completion_markandrefusal. Exit codes: 0 done (a clean dry run, a written row, or a reset already pending), 1 refused, 2 usage error.- To withdraw a reset before the next start, remove the manifest when
meta_gateway identity-censusshows the reset row as the directory’s only pending row. Never edit the file.
Session comms identity keys
Members’ comms identity keys live outside the state directory, one directory per session (<root>/<session_id>/identity.key and
identity.pub), where <root> is:
<dir>/.rkat/session_comms_identitywhen the gateway’sruntime_options.session_identity_config_rootnames<dir>;- on macOS,
~/Library/Application Support/meerkat/session_comms_identity; - on Linux,
$XDG_STATE_HOME/meerkat/session_comms_identity, or~/.local/state/meerkat/session_comms_identitywhenXDG_STATE_HOMEis not set; - on Windows,
%LOCALAPPDATA%\meerkat\session_comms_identity.
session_identity_config_root, reads the
same keys.
Migrate in place, as the same user on the same machine. In a state
directory moved or copied to another user or machine without these keys,
every restored member comes up broken, and the first send to one is refused:
session_comms_identity directory with it, to the root
the new user and machine resolve.
After a continuity-export import onto another host, members get new keys,
and peers that pinned the old keys see them as new identities.
Transcript repair is removed
meta_repair’s session transcript modes are removed: the default System-row
dedup, --keep-newest, --truncate-tool-results and
--drop-tool-results-older-than, with the --db, --session and
--all-sessions selectors. They rewrote transcripts in Meta’s 0.9.3
continuity copy, and 0.9.4 has no such copy and no replacement: Core
compaction manages the size of the transcript a member’s next turn reads (the
gateway’s compaction options set when it runs). Run any 0.9.3 repair you still
need before you migrate. In 0.9.4 each removed flag refuses:
meta_repair --composition-manifests and the meta-repair release archive
are unchanged.
Going back to 0.9.3
The migration is one-way.--apply converts runtime.sqlite in place to
Core’s head-canonical profile, which cannot be undone and which 0.9.3 refuses
to open, so 0.9.3 refuses a migrated directory instead of starting on it
without its sessions. A retired sessions.sqlite3 stamp is refused the same
way: 0.9.3 reads it as a schema from the future.
The way back to 0.9.3 is the copy you took before --apply (step 6 of
Before you upgrade): stop 0.9.4, put the copy of the
state directory back in place of the directory, and put the copy of a mob
store kept outside the directory back at its path, with its -wal file:
0.9.4 writes the mob store from its first boot on. Changes 0.9.4 made after
the migration are not carried back.
--apply also keeps a pre-apply backup of each database it changes, beside
it as <name>.pre-0.9.4-<timestamp>.native-migration, and the report prints
each path (backup: <path>): runtime.sqlite, and the projection database
it retires (sessions.sqlite3, continuity.sqlite3 or both). Each backup is one
consistent copy of its database, with what its -wal file held. The storage
doctor lists them, and they stay until you delete them with
meta_gateway storage-prune --state-dir <dir> --apply (by default it deletes
backups at least 30 days old). A run that started over leaves the stopped
run’s backups beside its own, under an earlier timestamp: going back uses
the ones the finished run’s report printed.
The backups are a way back only until 0.9.4 first starts on the directory:
from its first boot on, 0.9.4 writes the mob store (event_log.sqlite3 in
the directory, or the store you pass as --mob-store), which no backup
holds, so after that only the copy takes you back. Before that, with every
process stopped, copy each backup back to its own path and delete that
database’s -wal and -shm files, then start 0.9.3. Restore every backup:
with only runtime.sqlite back, 0.9.3 refuses the stamped sessions.sqlite3
or finds continuity.sqlite3 without its session rows, and with only the
projection back it still refuses the converted runtime.sqlite.
After an --apply that stopped part way, run --apply again to
finish, or go back:
- If the journal’s
donelist does not holdstart_conversion, nothing was written toruntime.sqliteand the databases are unchanged: 0.9.3 still starts on the directory (it does not read the journal), and a later--applystarts over. Deletestorage-migrate-native.journal.jsonand any partial.native-migrationfiles. - If it holds
start_conversion(a run killed during the conversion included), do not start 0.9.3 on the directory (it refuses once the conversion wrote Core’s claim): restore every backup the journal lists (deleting each database’s-waland-shmfiles), then delete the journal. Deleting the journal alone can leave a directory neither release reads. - Or put back the copy from step 6.
