/** * consumers.ts, every place that used to hold or guess a fact about the owner, * reading it from here instead. * * docs/owner-profile.md §13. One declared map from a CONSUMER CONFIG KEY to a * profile field, installed as a read fallback in `ConfigManager`. Not a set of * call-site edits, for two reasons that are both about not breaking things: * * - **Direction.** An explicitly configured value still wins; the profile fills * only the gap where the key is unset. A profile that overrode a value the * owner deliberately configured would be the drift class this design removes, * running backwards. * - **Contention.** The payments capability and `daemon.timezone` live on an * unmerged branch a live round owns. A map keyed by config path wires those * consumers the moment their keys exist, with no change to their code and no * edit to a file another round is holding. Rows for keys absent from today's * schema are inert and cost nothing, `ConfigManager.get()` never reaches the * fallback for a key whose section does not exist, because `resolvePath()` * throws first, and {@link profileFallbackStatus} catches that throw so a * report over the map cannot fail on the same rows. * * ## Where the fallback applies * * `ConfigManager.get()` only. Never a bulk listing, category read, dump or * export, see `config/profile-fallback.ts` for the rule and the reasoning: * a config dump resolving through the profile would hand a caller * `commerce.shippingAddress` when it asked for "the settings", without ever * passing the closed-tier disclosure rule. * * ## Deliberately NOT wired: `security/owner-identity.ts` * * `resolveOwnerAddresses()` decides which addresses count as "the owner's own", * and that set gates the single exemption to the content-taint rule. Its own * module header says the exemption is safe precisely because spoofing it needs * an authenticated write to daemon config, a strictly stronger capability than * sending mail. A profile written autonomously from conversation is a WEAKER * input than daemon config, so feeding it into that gate would lower a bar the * module documents as high. Recorded here as a decision, not an oversight. */ import { type ProfileRedactionValues } from '../utils/redaction.js'; import { profileFieldById } from './fields.js'; import type { OwnerProfileStore } from './store.js'; /** * One declared consumer wiring: which config key falls back to which profile * field, and, for a structured destination, which part of that value. */ export interface ConsumerFallbackRow { /** The consumer's config dot-path, e.g. `checkin.quietHours`. */ readonly configKey: string; /** The profile field id it falls back to, e.g. `contactMe.quietHours`. */ readonly fieldId: string; /** * For a config key that holds one PART of a postal address, which part. * Absent means the whole field value is the value. */ readonly addressPart?: PostalAddressPart | undefined; /** Why this row exists, in the words of §13's table. */ readonly note: string; } /** The seven parts a structured postal address is decomposed into. */ export type PostalAddressPart = 'name' | 'line1' | 'line2' | 'city' | 'region' | 'postalCode' | 'country'; /** * The whole map, exactly §13.1's table. * * Rows whose config key does not exist on this branch are inert: nothing calls * `get()` for a key that is not in the schema, so the row simply never fires. */ export declare const CONSUMER_FALLBACKS: readonly ConsumerFallbackRow[]; /** * Split a one-line address into its parts, best-effort, by comma. * * `200 Office Way, Lansing, MI 48933, US` → line1 / city / region+postalCode / * country. A shape this cannot read confidently yields `undefined` for the * parts it could not determine, and an undefined part means the consumer key * stays unset and falls back exactly as before. That is the correct failure * direction for an address: a key left unset is visible and fixable, while a * confidently-wrong `region` is a parcel delivered to the wrong state. * * `name` is never inferred, a profile line holds an address, not an addressee, * and guessing a recipient name out of a street line would be invention. */ export declare function splitPostalAddress(value: string): Partial>; /** The store reads the resolver needs. */ export type ConsumerProfileSource = Pick; /** Whether the fallback is switched on right now (`profile.consumerFallback`). */ export type FallbackEnabledPredicate = () => boolean; /** * The reader installed into `ConfigManager.attachProfileFallback`. * * Answers `undefined` for everything it has no declared row for, for a field * the owner has not recorded, and for a value the parser marked invalid, §4.3 * says an invalid mechanical value's consumer falls back exactly as if the * field were unset, and this is the place that promise is kept. */ export declare function createConsumerFallbackReader(source: ConsumerProfileSource, isEnabled: FallbackEnabledPredicate): (key: string) => unknown; /** One row's live state, for a settings surface that wants to show provenance. */ export interface ConsumerFallbackStatus { readonly configKey: string; readonly fieldId: string; /** True when the config key exists in this build's schema at all. */ readonly keyExists: boolean; /** True when the key is unset AND the profile has something for it. */ readonly resolvesFromProfile: boolean; } /** * Report which consumer keys currently resolve from the profile. * * §13.1: a listing may show THAT a key resolves from the profile; it does not * show the value, and nothing here returns one. The `get` call is wrapped * because `ConfigManager.resolvePath()` throws for a section that does not * exist, which is exactly the state of every `payments.*` row on this branch, * a report that threw on them would be a report nobody could run. */ export declare function profileFallbackStatus(source: ConsumerProfileSource, get: (key: string) => unknown): readonly ConsumerFallbackStatus[]; /** * The closed-tier strings a redactor should recognise, in two classes. * * `guarded` is everything ordinary: closed-tier mechanical field values, and * the prose of every CLOSED section. `profileSectionTier` makes every heading * except `Style` closed, including ones the owner invented, and an earlier * version of this function collected prose from only the four canonical * prose-only sections, so `note: Home is the blue house on the corner of Elm` * under a heading of their own left a session export in the clear while `People` * lines beside it were redacted. Closed means closed; the length and * distinctiveness floor in `utils/redaction.ts` is what stops an ordinary short * sentence becoming a corpus-wide pattern, and that floor is the right place * for that judgement rather than this section list. * * `absolute` is `People` only, and it deliberately bypasses that floor. §10 is * unconditional about third-party personal data, and the floor is keyed on the * SHAPE of a value, so a seven-character line like `- Bob Lee` slipped under it * and reached an export. Keying People on its section instead resolves the * conflict in the direction §10 requires, while everything else keeps the * protection against blanking `standard` out of every log line. Redaction * matches these on word boundaries so a very short name cannot eat the middle * of an unrelated word. * * Read through `read()`, not `section()`: the store's generic section accessor * refuses the closed tier on purpose, so no consumer assembling something can * enumerate `People`. Redaction is the opposite kind of caller, it is deciding * what must NOT leave, and `read()` is the disclosure path that returns the * whole document. */ export declare function closedTierRedactionValues(source: ConsumerProfileSource): ProfileRedactionValues; /** The seams a live profile is plugged into. All injectable, none imported back. */ export interface OwnerProfileConsumerHost { /** `ConfigManager.attachProfileFallback`. */ readonly attachProfileFallback: (reader: ((key: string) => unknown) | null) => void; /** Reads `profile.consumerFallback`, live, so the toggle is not a restart. */ readonly consumerFallbackEnabled: FallbackEnabledPredicate; /** Reads `profile.injectOpenTier`, live, for the same reason. */ readonly injectOpenTierEnabled: FallbackEnabledPredicate; } /** * Plug a loaded profile into every consumer seam, and return the undo. * * Four registrations, all reader-shaped so no consumer module gains an import * of this one: the `ConfigManager.get()` fallback, the redaction value source, * the signup base address, and the open-tier context block. Calling the returned * function clears all four, which is what a disposal scope needs in order to tear * a daemon down without leaving a dead closure holding the owner's addresses. * * Both live toggles are read through predicates rather than snapshotted, so * `profile.consumerFallback` and `profile.injectOpenTier` take effect on the * next read instead of on the next restart, they ship as real settings, and a * setting that needs a restart to mean anything is not one. */ export declare function installOwnerProfileConsumers(source: ConsumerProfileSource, host: OwnerProfileConsumerHost): () => void; /** Re-exported so a caller holding a config key can name its field in one import. */ export { profileFieldById }; //# sourceMappingURL=consumers.d.ts.map