/** Announce-once receipt sink (the manager binds FeatureAnnouncementStore.record). */ export type MigrationReceiptSink = (id: string, text: string) => void; /** * Whether the process running a pass OWNS the file it is migrating. * * ── The failure this closes ──────────────────────────────────────────────── * * `~/.goodvibes/daemon/settings.json` is written by the daemon and READ by * every terminal product, because a client resolves daemon-owned keys to show * and edit them. A pass that rewrites the file on load therefore runs in * processes that do not own it, and on a real machine those processes are not * the same version at the same moment. * * That is not hypothetical. A 2.0.5-runtime client loaded the daemon tier, * migrated `payments.budget.perPurchaseCeilingCents` to `perPurchaseCeiling` on * disk, and the still-running 1.28.6 daemon then read a key its build did not * know, skipped it, and stopped resolving a configured ceiling. The client was * right about the rename and had no business writing it. * * ── The rule ────────────────────────────────────────────────────────────── * * A process migrates ON DISK only the files it owns. A reader that does not own * the file migrates its in-memory view, so a new client reading an old file * still resolves the right values, and leaves the bytes alone. No receipt is * filed either: a receipt says "your file now reads this way", and with nothing * written that sentence would be false. * * `ownsFile` is explicit rather than inferred. Nothing about a path says who is * allowed to write it, and guessing from a surface root would make ownership a * naming convention instead of a decision the composing runtime states. */ export interface MigrationOwnership { /** True only in the runtime that owns this file on disk. */ readonly ownsFile: boolean; } /** The default for a pass over a file the loading process owns outright. */ export declare const OWNS_FILE: MigrationOwnership; /** danger.daemon=false → daemon.enabled=false (the removed alias's off-switch is honored). */ export declare function applyDangerDaemonMigrationPass(parsed: Record, sourcePath: string): Record; /** Legacy featureFlags entries dissolve onto their domain settings keys, with a receipt. */ export declare function applyLegacySettingsMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink): Record; /** orchestration.maxActiveAgents → fleet.maxSize ("Maximum fleet size"), with a receipt. */ export declare function applyFleetMaxSizeMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink): Record; /** controlPlane.baseUrl (a stored mirror of a derivable URL) is dropped, with a receipt. */ export declare function applyControlPlaneBaseUrlMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink): Record; /** Strip previously-frozen defaults from a whole-config dump (sparse files untouched), with a receipt. */ export declare function applyDefaultStripMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink): Record; /** daemon.embedInProcess (a choice no surface could act on) is dropped, with a receipt. */ export declare function applyDaemonEmbedInProcessMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink): Record; /** * `daemon.enabled: false` gets the other half of its old meaning written down: * `daemon.connectedHost.enabled: true`, with a receipt. * * The one key used to answer two questions, does this surface ADOPT a daemon * of its own, and may it DIAL one it is already connected to. Declining the * first silently declined the second, so a machine with `daemon.enabled: false` * and a live connected host had its session-inputs poll, rewind registration, * approvals stream and hosted-conversation handoff all refusing, while its * session spine, memory spine and operator tools dialed the same host fine. * * The new key's default already fixes the behaviour. This pass exists so the * user's file SAYS so: the decision they made is split in two on disk where * they can see both halves, rather than one of them quietly changing meaning * underneath a flag they set years ago. * * Ownership applies (see {@link MigrationOwnership}): a process that does not * own the file migrates its in-memory view and writes neither bytes nor a * receipt, because "your file now reads this way" would not be true. */ export declare function applyDaemonConnectedHostSplitMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink, ownership?: MigrationOwnership): Record; /** * The payments budget amounts move onto their new names, with their numbers * converted, and a receipt naming every key and both values. * * These four settings used to be named for, and stored as, the count of the * currency's smallest division, so a hundred was written `10000` and a limit * was one missing zero away from being a hundred times what its owner meant. * They now hold the amount itself. The receipt quotes what each key was and * what it is, because a spending limit silently changing shape is the one thing * this migration must never do quietly. * * This is the one pass with a NON-OWNING caller: it runs over the daemon tier, * which every client reads and only the daemon writes. See {@link * MigrationOwnership}, `ownsFile: false` applies the rename to the in-memory * view and writes neither the file nor a receipt. * * The owning write also records a reader floor. Ownership stops a client from * renaming keys under an older daemon; the floor covers the other direction, * where the DAEMON is newer, an older reader of the migrated file then names * the version and says to update, instead of skipping four limits it cannot * place. See PAYMENTS_BUDGET_AMOUNTS_READER_FLOOR. */ export declare function applyPaymentsBudgetMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink, ownership?: MigrationOwnership): Record; /** * The retired `occasions.finalStretchDays` is dropped, with a receipt. * * DAEMON-OWNED, CLIENT-READ, exactly like the payments rename beside it: the * `occasions.` prefix lives in the daemon tier, which every terminal product * reads and only the daemon writes. So ownership applies (see {@link * MigrationOwnership}), a non-owning reader drops the key from its in-memory * view and writes neither the bytes nor a receipt, because "your file now reads * this way" would not be true, and because a client rewriting daemon-owned keys * under an older daemon is the precise shape of the incident that made * ownership explicit in the first place. * * No reader floor is recorded, and the difference from the payments pass is * worth stating. That one RENAMED keys, so an older daemon reading the migrated * file would find names it could not place and silently skip four spending * limits, the floor exists to make that loud. This one only removes a key that * no longer governs anything. An older reader of the migrated file simply falls * back to a default for a setting whose value never reaches a decision, so * there is nothing for it to get wrong and nothing to refuse a start over. */ export declare function applyOccasionsFinalStretchMigrationPass(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink, ownership?: MigrationOwnership): Record; /** * Every load-time pass over the DAEMON TIER, in order. * * A separate sequence from {@link runLoadMigrationPasses} because it is a * separate file with separate rules. `~/.goodvibes/daemon/settings.json` holds * the daemon-owned keys, is written by the daemon and read by every terminal * product, and the surface-file passes describe shapes it does not have. * * Ownership is threaded through every pass rather than assumed, and it is the * lesson of the payments money-key incident: a 2.0.5 client migrated this file * on load while a 1.28.6 daemon was still running, and the daemon then skipped a * key its build did not know and stopped resolving a configured spending limit. * The client was right about the change and had no business writing it. * * These run BEFORE the ingestion key screen, see ingestSettingsFile's `migrate` * hook. A key one of these removes or renames is not a key the build fails to * understand, and screening first would report a state that is already handled. */ export declare function runDaemonTierMigrationPasses(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink, ownership: MigrationOwnership): Record; /** * Every load-time pass, in order, over one parsed settings file. * * The order lives here rather than at the call site because it is a property of * the passes: each runs on the output of the one before it, and the default-strip * pass has to come last so it sees the shape the others leave behind. * * OWNERSHIP: every pass in this sequence runs over a SURFACE settings file, * the global `~/.goodvibes//settings.json` and the project * `/.goodvibes//settings.json`. A surface file is written * and read by that surface's own process, so writer and owner are the same * runtime by construction and these passes take no ownership argument. The * daemon tier is the exception, and it is loaded separately, see * ConfigManager.loadDaemonTier and {@link MigrationOwnership}. */ export declare function runLoadMigrationPasses(parsed: Record, sourcePath: string, receipt: MigrationReceiptSink): Record; //# sourceMappingURL=manager-migration-passes.d.ts.map