/** * WHERE THE GOVERNANCE STORE LIVES — and where its dead letters go (crew#495, acceptance * finding F-022). One pure resolution the CLI, the adapter, the diagnostics route and the tests * share, in the shape of `interactive/bus-location.ts`. * * # The defect * * The engine's emit seam (`wicked-apps-core::emit::emit_event`) publishes every cross-product * `wicked.*` event — the conformance claims and decisions the acceptance gate reads back * (`wicked.crew.governance.conformance_recorded`), phase transitions, the steering-rule lifecycle * (`wicked.estate.rule.ingested` / `.retired`), council traffic — as an EVENT node on the shared * estate store named by `WICKED_ESTATE_DB`. `serve` never set that variable, so on EVERY default * install EVERY such event dead-lettered: appended to an NDJSON outbox under the operator's HOME * (`~/.something-wicked/wicked-apps/emit-outbox.ndjson`), shared by every daemon on the host, with * no timestamp, no origin, and nothing on `/diagnostics` or the console to say so — 3,400+ entries * on one host while the home board asserted "Governed 100%". * * # Resolution, in order * * 1. `--governance-db` / `WICKED_CREW_GOVERNANCE_DB` — the operator's explicit choice; * 2. an inherited `WICKED_ESTATE_DB` — the engine's own variable, honoured as-is (pointing several * daemons, or the estate MCP, at ONE shared store is exactly what it exists for); * 3. `.governance/governance.db` — a SIDECAR of the core db, the default. Why a sidecar * and not `/governance.db`: wicked-core embeds crew's state-home registry * (`tests/fixtures/state-home-subtrees.json`, core `src/state_home.rs`) as the worker Read * fence and REFUSES every launch that meets a top-level entry the registry does not classify. * The registry's `core.db` entry is a PREFIX claim (`file-with-sidecars`) that already covers a * `core.db.governance/` directory, exactly as it covers core's own `core.db.events/` and crew's * `core.db.bus/` (F-043). A new top-level name would need a byte-identical registry change in * BOTH repos and a core release before one governed run could start beside it; the sidecar * needs neither, and `GET /diagnostics.stores` lists it for free. The prefix claim is spelled * for the DEFAULT basename: a `--db` named anything but `core.db` already puts the db itself, * its `-wal`/`-shm`, core's own `.events/` and crew's `.bus/` outside the registry — * this sidecar is no different, so a daemon that hosts governed runs keeps the default * basename (the same residual `interactive/bus-location.ts` documents). * * Why NOT the core db itself: the single-writer actor holds `core.db` open, and the emit seam opens * its OWN connection per emit — pointing it at `core.db` would put a second writer on the actor's * store, the one race the single-writer design exists to rule out. * * # The dead-letter outbox * * When the store cannot be written (unopenable path, a write error) the engine spools the event to * the NDJSON outbox named by `WICKED_APPS_EMIT_DEADLETTER`. An explicit value in the daemon's * environment is honoured — it is the engine's own override, and the hermetic test harness relies * on it (`tests/setup/hermetic-home.ts`); otherwise the outbox is `.governance/ * emit-outbox.ndjson` — under the state home, never under HOME, never shared by two daemons — * whichever store won. `GET /diagnostics` folds it (`governance.deadletters`) and raises a * `governance.deadletter` finding whenever it holds an entry; `wicked-crew governance replay` * drains it back into the store. * * # Why exporting `WICKED_ESTATE_DB` process-wide is safe * * The napi engine runs in this process and reads the variable at emit time, so `process.env` is * the only channel. The ONLY in-process reader is the emit seam — the actor opens its store by the * explicit `--db` path, every governance binding takes its path as an argument. Governed workers * never inherit it: core strips it from every spawn (`spawn.rs` `hardened()`, FINDING-067) and * re-points a worker's estate channel at the repo's OWN graph (`execute_wrapped.rs`). Crew's own * children that would resolve a store from it get the BOOT-TIME value back * ({@link childEnvWithBootEstateDb}) — the operator's instruction, not the daemon's sidecar. */ /** The `serve` / `governance` flag naming the store explicitly. */ export declare const GOVERNANCE_DB_FLAG = "--governance-db"; /** The crew-side env override — the flag's environment spelling. */ export declare const GOVERNANCE_DB_ENV = "WICKED_CREW_GOVERNANCE_DB"; /** The ENGINE's variable: the shared estate store `wicked-apps-core::emit::emit_event` writes to * (`wicked_apps_core::ESTATE_DB_ENV`). Exported by the daemon; never typed out elsewhere. */ export declare const ESTATE_DB_ENGINE_ENV = "WICKED_ESTATE_DB"; /** The ENGINE's dead-letter outbox override (`wicked_apps_core::emit::DEADLETTER_ENV`). */ export declare const EMIT_DEADLETTER_ENGINE_ENV = "WICKED_APPS_EMIT_DEADLETTER"; /** The ENGINE's optional origin stamp for spooled entries (`wicked_apps_core::emit::ORIGIN_ENV`, * wicked-core ≥ the release carrying crew#495's companion): copied verbatim onto every * dead-letter record as `origin`, beside the engine's own `ts` (epoch ms) and `pid`. An older * engine ignores it — entries then carry no timestamp, which the fold reports honestly. */ export declare const EMIT_ORIGIN_ENGINE_ENV = "WICKED_APPS_EMIT_ORIGIN"; /** The sidecar directory's suffix and the two files it holds. */ export declare const GOVERNANCE_SIDECAR_SUFFIX = ".governance"; export declare const GOVERNANCE_DB_FILENAME = "governance.db"; export declare const EMIT_OUTBOX_FILENAME = "emit-outbox.ndjson"; /** * The value of the engine's store variable when THIS PROCESS booted — the operator's instruction, * captured before the daemon exports its own resolution over it. Children that resolve a store from * the variable are handed this back ({@link childEnvWithBootEstateDb}). */ export declare const BOOT_ESTATE_DB: string | undefined; /** What decided the store — logged at boot and reported on `/diagnostics` so an operator can see which rule won. */ export type GovernanceStoreSource = 'flag' | 'env-crew' | 'env-estate' | 'core-db-sidecar'; /** What decided the outbox: the engine's own override in the environment, or the sidecar default. */ export type GovernanceOutboxSource = 'env' | 'core-db-sidecar'; export interface GovernanceStoreLocation { /** The estate store the engine's emit seam writes to — exported as `WICKED_ESTATE_DB`. An * absolute SQLite path, or `:memory:` (the engine's "no shared store"). The emit seam is * SQLite-only (`wicked-apps-core::emit::emit_event` → `open_store` → `SqliteStore::open`), so a * URL spec is REFUSED at resolution ({@link GovernanceStoreError}) — never handed to an engine * that would dead-letter every event and print the spec, credentials included, on stderr. */ dbPath: string; /** What operator-facing surfaces print — the boot log, the readiness line, * `/diagnostics.governance.store.path`, `governance replay` output. Equal to `dbPath` for every * value the resolver admits; the userinfo redaction ({@link redactStoreSpec}) is defence in depth. */ displayPath: string; source: GovernanceStoreSource; /** The dead-letter outbox — exported as `WICKED_APPS_EMIT_DEADLETTER`. */ outboxPath: string; outboxSource: GovernanceOutboxSource; /** `.governance` — created at boot so the engine's first write never fails on a missing parent. */ sidecarDir: string; /** The daemon's core db, absolute — what `governance replay --db` names to reach the SAME sidecar. */ coreDbPath: string; } export interface GovernanceStoreInput { /** `--governance-db` (flag), or undefined. */ flagDb?: string | undefined; /** `WICKED_CREW_GOVERNANCE_DB`, or undefined/empty. */ envCrewDb?: string | undefined; /** An inherited `WICKED_ESTATE_DB`, or undefined/empty. */ envEstateDb?: string | undefined; /** An inherited `WICKED_APPS_EMIT_DEADLETTER`, or undefined/empty. */ envOutbox?: string | undefined; /** The daemon's core db path (`--db`, or the default under the state home). */ coreDbPath: string; /** The daemon's cross-product bus db (`resolveCrewBus().dbPath`) — refused as a governance store too. */ busDbPath?: string | undefined; } /** A governance store the engine could not honour — a boot error, not a warning: the alternative is a * daemon that dead-letters 100 % of its governance events (a URL spec on the SQLite-only emit seam) * or a second writer on the actor's `core.db` / the bus db (the race the single-writer design * exists to rule out). Same shape as `interactive/bus-location.ts`'s `CrewBusError`. */ export declare class GovernanceStoreError extends Error { constructor(message: string); } /** * The ONE-LINE FIX for a daemon that resolved no governance store (crew#829): what to restart it * with. Said wherever the missing store is reported — the `/diagnostics` finding, the replay route's * refusal — instead of a replay recipe: with no store there is nothing to replay into, and a bare * `wicked-crew governance replay ` would target the DEFAULT state home's sidecar * (`replayTarget`'s `--db` default), not this daemon's. `wicked-crew serve` resolves the sidecar by * itself, so a store-less daemon is an embedded/library boot that passed no location. */ export declare function governanceStoreFix(): string; /** The sidecar directory the daemon's governance store and outbox live in: `.governance`. */ export declare function governanceSidecarDir(coreDbPath: string): string; /** The default store: `.governance/governance.db`. */ export declare function governanceSidecarDb(coreDbPath: string): string; /** The default outbox: `.governance/emit-outbox.ndjson`. */ export declare function governanceSidecarOutbox(coreDbPath: string): string; /** Whether a store value is a URL spec (`postgres://…`) — something the emit seam cannot open. */ export declare function isUrlSpec(value: string): boolean; /** A store spec the engine parses itself rather than a filesystem path — `:memory:`, and nothing * else: the emit seam is SQLite-only, so a URL spec is refused before it gets this far. Left * exactly as written, never resolved, never `mkdir`ed for. */ export declare function isStoreSpec(value: string): boolean; /** DEFENCE IN DEPTH: a value safe to print — a URL's credentials become `***` (`postgres://u:p@h/db` * → `postgres://***@h/db`); `:memory:` and filesystem paths are returned unchanged. URL specs are * refused at resolution, so in practice this is the identity; it stays so that no future rung can * print a credential by accident (the refusal message itself uses it). */ export declare function redactStoreSpec(value: string): string; /** * The CANONICAL spelling of a path that may not exist yet: the real path of its nearest existing * ancestor plus the remaining segments — so a symlink to `core.db` (or to its directory) compares * equal to `core.db` itself, which `resolve()` alone (lexical `..` folding) cannot see. Fails * closed: anything but "does not exist" while walking up is a {@link GovernanceStoreError}. */ export declare function canonicalPath(p: string): string; /** Resolve the governance store and its outbox for one daemon — pure, so two `--db` inputs can be compared. */ export declare function resolveGovernanceStore(input: GovernanceStoreInput): GovernanceStoreLocation; /** * Hand the resolved location to the engine: export the two engine variables into `env` and create * the sidecar directory (the engine's SQLite open does not create a missing parent, and a spool * into a missing directory is a dead letter that itself fails). Called ONCE per daemon, before the * engine exists (`CoreAdapter`'s constructor) — the emit seam reads the variables at emit time, * so nothing needs a restart. A library boot that never resolves a location exports nothing, and * the hermetic test arming stays exactly as it was. */ export declare function applyGovernanceStoreEnv(location: GovernanceStoreLocation, env?: NodeJS.ProcessEnv): void; /** * The origin stamp the engine copies onto every dead-letter record — WHICH daemon spooled it, so a * record read months later in a drained outbox still says where it came from. Human-readable on * purpose; the port is appended once the daemon has bound (the engine reads the variable at emit * time, so the update is seen by the next spool). */ export declare function emitOrigin(fields: { version: string; pid: number; coreDbPath: string; port?: number | undefined; }): string; /** Export the origin stamp for the engine. */ export declare function applyEmitOrigin(origin: string, env?: NodeJS.ProcessEnv): void; /** * The environment for EVERY child crew spawns (git, uv, the estate CLI and MCP, the deliver shell, * the interactive bridge, the OS opener, `ps`/`lsof`): the daemon's exported governance store is the * in-process engine's business and nobody else's. `WICKED_ESTATE_DB` goes back to whatever the * process booted with — the operator's explicit store, or nothing (a newer estate binary handed the * daemon's `governance.db` as its graph store could migrate the file's schema past what the engine's * vendored store opens) — and crew's own override `WICKED_CREW_GOVERNANCE_DB`, needed only at * resolution, is dropped outright. Governed workers are covered separately by core's `hardened()`. */ export declare function childEnvWithBootEstateDb(env?: NodeJS.ProcessEnv, bootValue?: string | undefined): NodeJS.ProcessEnv; /** * Where a PRE-FIX engine dead-lettered under HOME — `/.something-wicked/ * wicked-apps/emit-outbox.ndjson`, the engine's own default (`emit.rs` `deadletter_path()`), spelled * here only so the daemon can TELL the operator it exists (a boot warning and a `/diagnostics` * finding with the replay command). Crew never writes it. `null` when no home resolves. */ export declare function legacyHomeOutboxPath(env?: NodeJS.ProcessEnv): string | null; /** * Whose dead letters the HOME outbox holds, as far as THIS daemon can tell (F-2R2-006). * * - `'own'` — this daemon runs in the DEFAULT state home of the HOME the outbox was found under * (`/.wicked-crew`), so the pre-fix daemons that spooled there were its own * earlier versions: the outbox is its prior outbox, and replaying it into this * daemon's store is the repair. * - `'host'` — this daemon runs in an ISOLATED state home (`--db` elsewhere, a fresh rig, a test * harness). A pre-fix engine wrote under HOME regardless of which daemon it served, * so the file is shared by every daemon on the host and CANNOT be attributed to this * one — it may hold other daemons' dead letters, or (for an isolated daemon that ran * since before the fix) some of its own, and nothing in the file says which (the * post-fix `origin` stamp, `emitOrigin`, would — a fold over it is the follow-up). * Reporting it as a warning with a replay recipe would import a stranger's governance * events into this store; it is reported at info with the read-only inspect recipe. * * `coreDbPath` `null` (no store resolved) is `'host'`: with no state home there is nothing to * attribute the file to. Pure — the HOME is read off the outbox path itself * (`/.something-wicked/wicked-apps/`), never off the process environment. */ export type LegacyOutboxScope = 'own' | 'host'; export declare function legacyOutboxScope(coreDbPath: string | null, legacyOutboxPath: string): LegacyOutboxScope; //# sourceMappingURL=governance-store.d.ts.map