/** * config-replica.ts, the replicated settings document, and how two copies of * it are reconciled. * * ── ORDERING IS NOT A CLOCK ──────────────────────────────────────────────── * * Nothing here compares timestamps to decide what wins. Two homelab machines * routinely disagree about the time by minutes, neither is obliged to run NTP, * and a last-write-wins rule keyed on wall clock would let the machine with the * fastest clock win every conflict permanently, including winning against a * deletion made afterwards on a correct clock. * * Ordering is a REVISION: a counter the master increments. Higher revision * wins. At an equal revision, which only a partition with two masters can * produce, a deletion beats a write, and two writes are settled by origin node * id so both sides of a heal land on the same answer without negotiating. * * `at` is recorded because an operator reading `cluster status` wants to know * when something changed. It is never consulted to decide what wins, and it * must not start being. */ /** One replicated setting. */ export interface ConfigReplicaEntry { readonly path: string; /** JSON value for a config path; for a secret, the ciphertext is never here. */ readonly value: unknown; readonly revision: number; /** The node that originated the change, logged, and the equal-revision tiebreak. */ readonly origin: string; /** Wall clock, for display only. Never used for ordering. */ readonly at: number; /** True when the value lives in the secret store rather than in config. */ readonly secret: boolean; } /** * A deletion. * * The same discipline the roster uses for a removed machine, and for the same * reason: absence is indistinguishable from "not learned yet", so a deletion has * to be a positive fact that survives a merge. Without this, a machine that was * partitioned when the operator deleted a surface would helpfully put it back. */ export interface ConfigReplicaTombstone { readonly path: string; readonly revision: number; readonly origin: string; readonly at: number; readonly secret: boolean; } export interface ConfigReplicaDocument { readonly version: 1; readonly groupId: string; /** Highest revision this copy has issued or seen. */ readonly revision: number; readonly entries: readonly ConfigReplicaEntry[]; readonly tombstones: readonly ConfigReplicaTombstone[]; } /** * Bounds. Persisted, replicated state that can only grow is a slow leak, and * one that arrives over the network is a leak someone else can drive. */ export declare const MAX_REPLICATED_ENTRIES = 512; export declare const MAX_REPLICATED_TOMBSTONES = 256; export declare const CONFIG_TOMBSTONE_MAX_AGE_MS: number; /** A single replicated value, serialized. Comfortably past any real setting. */ export declare const MAX_REPLICATED_VALUE_BYTES: number; /** True when a value is small enough and plain enough to replicate. */ export declare function isReplicableValue(value: unknown): boolean; /** An empty document for a group that has replicated nothing yet. */ export declare function createConfigReplicaDocument(groupId: string): ConfigReplicaDocument; /** * Parse an untrusted document, off the wire, or off disk after a crash. * * `keep` is the replication policy. Filtering on the RECEIVE side as well as * the send side is the point: a peer running a different build, or a modified * one, does not get to decide what this machine will apply to its own config. */ export declare function readConfigReplicaDocument(value: unknown, keep: (path: string) => boolean): ConfigReplicaDocument | null; /** Reconcile two copies. Commutative: the merge order does not change the result. */ export declare function mergeConfigReplica(local: ConfigReplicaDocument, remote: ConfigReplicaDocument): ConfigReplicaDocument; /** Write (or overwrite) a setting at the next revision. */ export declare function putReplicaEntry(document: ConfigReplicaDocument, input: { path: string; value: unknown; origin: string; at: number; secret?: boolean; }): ConfigReplicaDocument; /** Delete a setting at the next revision. */ export declare function deleteReplicaEntry(document: ConfigReplicaDocument, input: { path: string; origin: string; at: number; secret?: boolean; }): ConfigReplicaDocument; export interface ConfigReplicaSweepResult { readonly document: ConfigReplicaDocument; readonly droppedTombstones: number; } /** * Bound the document. * * Deletions expire after {@link CONFIG_TOMBSTONE_MAX_AGE_MS} and are capped, * oldest first. Expiring one is safe for the same reason it is safe in the * roster: the deletion has already been applied everywhere that is still * talking, and the tombstone's only job is to outlive a partition, 90 days is * far longer than any partition that ends in a heal rather than a rebuild. */ export declare function sweepConfigReplica(document: ConfigReplicaDocument, now: number): ConfigReplicaSweepResult; /** The entry for a path, or null. */ export declare function findReplicaEntry(document: ConfigReplicaDocument, path: string): ConfigReplicaEntry | null; //# sourceMappingURL=config-replica.d.ts.map