@agentcomms/resend 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/cli.mjs +29 -8
- package/dist/index.d.mts +749 -95
- package/dist/index.mjs +2 -2
- package/dist/{send-DtBNDnE4.mjs → send-9pskzeUY.mjs} +7730 -4528
- package/dist/{server-B5FPOBcz.mjs → server-DFbgMT3K.mjs} +29 -5
- package/dist/{stdio-entry-CUFTANFM.mjs → stdio-entry-PsvT2AI2.mjs} +1 -1
- package/package.json +4 -3
package/dist/index.d.mts
CHANGED
|
@@ -4322,7 +4322,25 @@ declare namespace external_d_exports {
|
|
|
4322
4322
|
export { $brand, $input, $output, BRAND, CompileOptions, GlobalMeta, INVALID, Infer$1 as Infer, IssueData, JSONType, NEVER, $RefinementCtx as RefinementCtx, SafeExtendShape, Schema, TimePrecision, TypeOf, ZodAny, ZodArray, ZodBase64, ZodBase64URL, ZodBigInt, ZodBigIntFormat, ZodBoolean, ZodCIDRv4, ZodCIDRv6, ZodCUID, ZodCUID2, ZodCatch, ZodCodec, ZodCoercedBigInt, ZodCoercedBoolean, ZodCoercedDate, ZodCoercedNumber, ZodCoercedString, ZodCompileAsyncError, ZodCompileUnsupportedError, ZodCreditCard, ZodCustom, ZodCustomStringFormat, ZodDate, ZodDefault, ZodDiscriminatedUnion, ZodE164, ZodEmail, ZodEmoji, ZodEnum, ZodError, ZodErrorMap, ZodExactOptional, ZodFile, ZodFirstPartySchemaTypes, ZodFirstPartyTypeKind, ZodFlattenedError, ZodFloat32, ZodFloat64, ZodFormattedError, ZodFunction, ZodGUID, ZodIBAN, ZodIPv4, ZodIPv6, ZodISODate, ZodISODateTime, ZodISODuration, ZodISOTime, ZodInstanceOf, ZodInt, ZodInt32, ZodInt64, ZodIntersection, ZodIssue, ZodIssueCode, ZodJSONSchema, ZodJSONSchemaInternals, ZodJWT, ZodKSUID, ZodLazy, ZodLiteral, ZodMAC, ZodMap, ZodNaN, ZodNanoID, ZodNever, ZodNonOptional, ZodNull, ZodNullable, ZodNumber, ZodNumberFormat, ZodObject, ZodOptional, ZodPipe, ZodPrefault, ZodPreprocess, ZodPromise, ZodRawShape$1 as ZodRawShape, ZodReadonly, ZodRealError, ZodRecord, ZodSafeParseError, ZodSafeParseResult, ZodSafeParseSuccess, ZodSchema, ZodSet, ZodStandardSchemaWithJSON, ZodString, ZodStringFormat, ZodSuccess, ZodSymbol, ZodTemplateLiteral, ZodTransform, ZodTuple, ZodType, ZodTypeAny, ZodUInt32, ZodUInt64, ZodULID, ZodURL, ZodUUID, ZodUndefined, ZodUnion, ZodUnknown, ZodVoid, ZodXID, ZodXor, _ZodBigInt, _ZodBoolean, _ZodDate, _ZodNumber, _ZodString, _ZodType, _default, _function, any, array, base64, base64url, bigint$1 as bigint, boolean$1 as boolean, _catch as catch, check, cidrv4, cidrv6, clone, codec, coerce_d_exports as coerce, compile, config, index_d_exports$1 as core, creditCard, cuid, cuid2, currencyCode, custom, date$2 as date, decode, decodeAsync, deepPartial, describe, discriminatedUnion, e164, email, emoji, encode, encodeAsync, _endsWith as endsWith, _enum as enum, exactOptional, file, flattenError, float32, float64, formatError, fromJSONSchema, _function as function, getDiscriminatedOption, getErrorMap, globalRegistry, _gt as gt, _gte as gte, guid, hash, hex, hostname, httpUrl, iban, _includes as includes, output$1 as infer, inferFlattenedErrors, inferFormattedError, input, _instanceof as instanceof, int, int32, int64, intersection, invertCodec, ipv4, ipv6, iso_d_exports as iso, json, jwt, keyof, ksuid, lazy, _length as length, literal, index_d_exports$2 as locales, looseObject, looseRecord, _lowercase as lowercase, _lt as lt, _lte as lte, mac, map, _maxLength as maxLength, _maxSize as maxSize, memoizer, meta, _mime as mime, _minLength as minLength, _minSize as minSize, _multipleOf as multipleOf, nan, nanoid, nativeEnum, _negative as negative, never, _nonnegative as nonnegative, nonoptional, _nonpositive as nonpositive, _normalize as normalize, _null as null, nullable, nullish, number$1 as number, object, optional, output, _overwrite as overwrite, parse, parseAsync, partialRecord, pipe, _positive as positive, prefault, preprocess, prettifyError, promise, _properties as properties, _property as property, readonly, record, refine, _regex as regex, regexes_d_exports as regexes, registry, safeDecode, safeDecodeAsync, safeEncode, safeEncodeAsync, safeParse, safeParseAsync, set, setErrorMap, _size as size, _slugify as slugify, _startsWith as startsWith, strictObject, string$1 as string, stringFormat, stringbool, success, superRefine, symbol, templateLiteral, toJSONSchema, _toLowerCase as toLowerCase, _toUpperCase as toUpperCase, toZod, transform, treeifyError, _trim as trim, tuple, uint32, uint64, ulid, _undefined as undefined, union, unknown, _uppercase as uppercase, url, util_d_exports as util, uuid, uuidv4, uuidv6, uuidv7, validate, validateAsync, _void as void, withParser, xid, xor };
|
|
4323
4323
|
}
|
|
4324
4324
|
//#endregion
|
|
4325
|
-
//#region ../core/dist/change-flow-
|
|
4325
|
+
//#region ../core/dist/change-flow-mr6T-6nU.d.mts
|
|
4326
|
+
//#region src/config-hooks.d.ts
|
|
4327
|
+
/**
|
|
4328
|
+
* Test-only pauses inside `ConfigStore.convertToVersion3`, for the tests that prove a write racing the conversion is
|
|
4329
|
+
* neither lost nor let in between its scan and its write.
|
|
4330
|
+
*
|
|
4331
|
+
* Deliberately not exported from the package root, as `enableNamesMigrationForTests` is not (`release-gate.ts`): a
|
|
4332
|
+
* store is given them through its constructor under this symbol, which only a test importing this file by path can
|
|
4333
|
+
* name. No consumer of the package can reach it.
|
|
4334
|
+
*/
|
|
4335
|
+
declare const CONVERSION_HOOKS: unique symbol;
|
|
4336
|
+
interface ConversionHooks {
|
|
4337
|
+
/** After the read before the lock, before the config lock is taken. */
|
|
4338
|
+
beforeLock?: (() => Promise<void> | void) | undefined;
|
|
4339
|
+
/** Under the lock, after the approvals were scanned and before the version-3 write is built. */
|
|
4340
|
+
afterScan?: (() => Promise<void> | void) | undefined;
|
|
4341
|
+
/** Under the lock, immediately before the version-3 write. */
|
|
4342
|
+
beforeWrite?: (() => Promise<void> | void) | undefined;
|
|
4343
|
+
}
|
|
4326
4344
|
interface RegisteredServer {
|
|
4327
4345
|
client: string;
|
|
4328
4346
|
path: string;
|
|
@@ -4396,6 +4414,20 @@ interface ResolvedPaths {
|
|
|
4396
4414
|
*/
|
|
4397
4415
|
downloadsDir: string;
|
|
4398
4416
|
}
|
|
4417
|
+
type ErrorCode = "UNEXPECTED" | "APPROVAL_REQUIRED" | "APPROVAL_PENDING" | "APPROVAL_EXPIRED" | "APPROVAL_VOID" | "SEND_OUTCOME_UNKNOWN" | "SEND_REFUSED" | "POLICY_NEVER" | "RATE_CAPPED" | "UNSENDABLE_HTML" | "LOOSENING_REFUSED" | "UPDATE_REQUIRED" | "USAGE" | "CURSOR_MISMATCH" | "BAD_DATA" | "REPLY_INVALID" | "NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "SECRET_STORE_UNAVAILABLE" | "TRANSIENT" | "KEYCHAIN_APPROVAL_PENDING" | "LOCK_TIMEOUT" | "AUTH_REQUIRED" | "SCOPE_MISSING" | "CONFIG";
|
|
4418
|
+
interface CommsErrorOptions {
|
|
4419
|
+
hint?: string;
|
|
4420
|
+
details?: Record<string, unknown>;
|
|
4421
|
+
cause?: unknown;
|
|
4422
|
+
}
|
|
4423
|
+
declare class CommsError extends Error {
|
|
4424
|
+
readonly code: ErrorCode;
|
|
4425
|
+
readonly hint: string | undefined;
|
|
4426
|
+
readonly details: Record<string, unknown> | undefined;
|
|
4427
|
+
constructor(code: ErrorCode, message: string, options?: CommsErrorOptions);
|
|
4428
|
+
get exitCode(): number;
|
|
4429
|
+
get retryable(): boolean;
|
|
4430
|
+
}
|
|
4399
4431
|
//#endregion
|
|
4400
4432
|
//#region src/cli-command.d.ts
|
|
4401
4433
|
/**
|
|
@@ -4813,7 +4845,49 @@ interface ConfigV2 extends ConfigBody {
|
|
|
4813
4845
|
*/
|
|
4814
4846
|
organisations?: Record<string, OrganisationRecord> | undefined;
|
|
4815
4847
|
}
|
|
4816
|
-
|
|
4848
|
+
/** What a legacy drain records of one tracked record: still open, or what became of it. */
|
|
4849
|
+
type LegacyDrainOutcome = "revoked" | "expired" | "used" | "failed" | "sending" | "unknown";
|
|
4850
|
+
/**
|
|
4851
|
+
* The approvals an earlier release prepared that were still waiting, or already being sent, when this configuration
|
|
4852
|
+
* became version 3, and what has become of each (design 2026-10-05 §D1, "Old releases are locked out before the fence
|
|
4853
|
+
* is relied on").
|
|
4854
|
+
*/
|
|
4855
|
+
interface LegacyDrain {
|
|
4856
|
+
/** When the conversion that opened the drain was written. */
|
|
4857
|
+
since: string;
|
|
4858
|
+
/**
|
|
4859
|
+
* Every tracked version-1 send record by id: `open` until it has an outcome — or `sending` from the start, for one an
|
|
4860
|
+
* earlier release was already sending when the conversion ran.
|
|
4861
|
+
*/
|
|
4862
|
+
tracked: Record<string, "open" | LegacyDrainOutcome>;
|
|
4863
|
+
}
|
|
4864
|
+
/** What version 3 adds to a body of either naming. */
|
|
4865
|
+
interface Version3Fields {
|
|
4866
|
+
/** Each owner's send epoch by id; absent reads as 0. Raised by every write that turns its send policy to `never`. */
|
|
4867
|
+
sendEpochs?: Record<string, number> | undefined;
|
|
4868
|
+
/** Present while records from an earlier release are being retired. */
|
|
4869
|
+
legacyDrain?: LegacyDrain | undefined;
|
|
4870
|
+
}
|
|
4871
|
+
/** Version 3 over a version-1 body: one plain word per account. */
|
|
4872
|
+
interface ConfigV3Naming1 extends ConfigBody, Version3Fields {
|
|
4873
|
+
version: 3;
|
|
4874
|
+
naming: 1;
|
|
4875
|
+
}
|
|
4876
|
+
/** Version 3 over a version-2 body: `organisation/platform` names, and the names they replaced. */
|
|
4877
|
+
interface ConfigV3Naming2 extends Omit<ConfigV2, "version">, Version3Fields {
|
|
4878
|
+
version: 3;
|
|
4879
|
+
naming: 2;
|
|
4880
|
+
}
|
|
4881
|
+
/**
|
|
4882
|
+
* Version 3: the send epoch and the legacy drain (design 2026-10-05 §D1), over either naming — converted from version 1
|
|
4883
|
+
* or 2 without renaming anything, so its body keeps the rules of the version it came from.
|
|
4884
|
+
*/
|
|
4885
|
+
type ConfigV3 = ConfigV3Naming1 | ConfigV3Naming2;
|
|
4886
|
+
type Config$1 = ConfigV1 | ConfigV2 | ConfigV3;
|
|
4887
|
+
/** A configuration whose accounts are named `organisation/platform`: version 2, or version 3 over it. */
|
|
4888
|
+
type NamedConfig = ConfigV2 | ConfigV3Naming2;
|
|
4889
|
+
/** A configuration whose accounts have one plain word each: version 1, or version 3 over it. */
|
|
4890
|
+
type PlainConfig = ConfigV1 | ConfigV3Naming1;
|
|
4817
4891
|
/** The committed config returned by an update; process-only commit metadata is deliberately stored out of band. */
|
|
4818
4892
|
type ConfigUpdateResult = Config$1;
|
|
4819
4893
|
declare class ConfigStore {
|
|
@@ -4825,6 +4899,8 @@ declare class ConfigStore {
|
|
|
4825
4899
|
*/
|
|
4826
4900
|
constructor(configDir: string, options?: {
|
|
4827
4901
|
readonly handoffs?: CliHandoffs | undefined;
|
|
4902
|
+
/** Test-only pauses inside the version-3 conversion (`config-hooks.ts`); unreachable from the package root. */
|
|
4903
|
+
readonly [CONVERSION_HOOKS]?: ConversionHooks | undefined;
|
|
4828
4904
|
});
|
|
4829
4905
|
/** The current config; an empty one when the file does not exist yet. */
|
|
4830
4906
|
load(): Promise<Config$1>;
|
|
@@ -4862,11 +4938,31 @@ declare class ConfigStore {
|
|
|
4862
4938
|
*
|
|
4863
4939
|
* The file it replaces is copied beside it first — see `backUpBeforeMigration` — and the copy's path returned.
|
|
4864
4940
|
*/
|
|
4865
|
-
migrateNames(expected: string, rows: readonly RenamedAccount[], build: (current:
|
|
4941
|
+
migrateNames(expected: string, rows: readonly RenamedAccount[], build: (current: PlainConfig) => NamedConfig): Promise<{
|
|
4866
4942
|
status: "migrated" | "already-migrated";
|
|
4867
|
-
config:
|
|
4943
|
+
config: NamedConfig;
|
|
4868
4944
|
backup?: string;
|
|
4869
4945
|
}>;
|
|
4946
|
+
/**
|
|
4947
|
+
* The one way a configuration becomes version 3 (design 2026-10-05 §D1, "Old releases are locked out before the
|
|
4948
|
+
* fence is relied on").
|
|
4949
|
+
*
|
|
4950
|
+
* Version 3 carries the send epoch, which 0.13 does not know, and 0.13 refuses to read it — so from this write on, no
|
|
4951
|
+
* 0.13 process can start a prepare, a claim, an approval or a policy change through this file. Nothing is renamed:
|
|
4952
|
+
* a version-1 body stays version 1's (`naming: 1`) until the names migration, which needs its own approval.
|
|
4953
|
+
*
|
|
4954
|
+
* Under the config lock, the file is read again — never from the cache, never from the read before the lock — and an
|
|
4955
|
+
* existing version 3 returned unchanged: a second converter that lost the race keeps the winner's epochs and drain.
|
|
4956
|
+
* `scan` runs under the same lock, so a record written while this waited for it is seen, and an update cannot land
|
|
4957
|
+
* between the scan and the write; the version-3 write carries `legacyDrain` only when the scan tracked something.
|
|
4958
|
+
* With no config file at all there is nothing to convert, and nothing is written.
|
|
4959
|
+
*/
|
|
4960
|
+
convertToVersion3(scan: () => Promise<LegacyDrain["tracked"]>, options?: {
|
|
4961
|
+
now?: (() => Date) | undefined;
|
|
4962
|
+
}): Promise<{
|
|
4963
|
+
status: "converted" | "already" | "empty";
|
|
4964
|
+
config: Config$1;
|
|
4965
|
+
}>;
|
|
4870
4966
|
}
|
|
4871
4967
|
/** One account's rename, as the migration planned it. */
|
|
4872
4968
|
interface RenamedAccount {
|
|
@@ -4928,6 +5024,373 @@ interface LooseningConsent {
|
|
|
4928
5024
|
*/
|
|
4929
5025
|
changes?: readonly Loosening[] | undefined;
|
|
4930
5026
|
}
|
|
5027
|
+
//#endregion
|
|
5028
|
+
//#region src/approval-binding.d.ts
|
|
5029
|
+
/**
|
|
5030
|
+
* What a version-2 approval binds, defined once (design 2026-10-05 §D1, "Digest integrity").
|
|
5031
|
+
*
|
|
5032
|
+
* A record stores two digests. `contentDigest` is the outward content or change, computed as before: the message, the
|
|
5033
|
+
* change, the download. `bindingDigest` is this module's: the content digest together with the record's route, its
|
|
5034
|
+
* lifetimes and its own identity — who it belongs to, which draft, which channel, which send epoch. Every one of those
|
|
5035
|
+
* is a field a claim or a report acts on, so a record whose binding no longer recomputes from its own fields has been
|
|
5036
|
+
* altered since it was made, and nothing it says can be trusted.
|
|
5037
|
+
*
|
|
5038
|
+
* There is no second copy of anything: the binding is recomputed from the stored fields alone, never stored apart
|
|
5039
|
+
* from them as a key.
|
|
5040
|
+
*/
|
|
5041
|
+
/**
|
|
5042
|
+
* How an approval is decided, fixed when it is made: `chat` — a yes in the conversation is enough — or `confirm` — a
|
|
5043
|
+
* person outside the chat decides. A send's route is the stricter of its live send policy and any escalation; a
|
|
5044
|
+
* change's is the live change policy. It never changes when the configuration does.
|
|
5045
|
+
*/
|
|
5046
|
+
type ApprovalRoute = "chat" | "confirm";
|
|
5047
|
+
/**
|
|
5048
|
+
* What a record's `inboxId` names. `owner` — a mailbox or account that existed when it was made (every send and
|
|
5049
|
+
* download, and a change to one). `prospective` — a change that creates its owner, which has no id yet. `global` — a
|
|
5050
|
+
* change to no owner at all, stored with `inboxId: ''`.
|
|
5051
|
+
*/
|
|
5052
|
+
type OwnerScope = "owner" | "prospective" | "global";
|
|
5053
|
+
/**
|
|
5054
|
+
* The file operations a lock is made of, one method each, so a test can count and interrupt every one of them: the
|
|
5055
|
+
* create that is the attempt, the read that checks a holder, the touch that renews, the unlink that releases.
|
|
5056
|
+
* `withFileLock` uses the real ones; `tryFileLock` takes another for a caller whose every step is counted.
|
|
5057
|
+
*/
|
|
5058
|
+
interface LockIo {
|
|
5059
|
+
/** Creates `path` exclusively (`wx`, 0600) holding `body`: the one attempt to take the lock. */
|
|
5060
|
+
create(path: string, body: string): Promise<void>;
|
|
5061
|
+
/** The lock file's text: a holder's body. */
|
|
5062
|
+
read(path: string): Promise<string>;
|
|
5063
|
+
/** The lock file's modification time, in milliseconds: where a renewal shows. */
|
|
5064
|
+
mtime(path: string): Promise<number>;
|
|
5065
|
+
/** Sets the lock file's times to now: a renewal. */
|
|
5066
|
+
touch(path: string): Promise<void>;
|
|
5067
|
+
rename(from: string, to: string): Promise<void>;
|
|
5068
|
+
link(from: string, to: string): Promise<void>;
|
|
5069
|
+
/** Removes a lock file, or one moved aside; a missing one is no error. */
|
|
5070
|
+
remove(path: string): Promise<void>;
|
|
5071
|
+
}
|
|
5072
|
+
//#endregion
|
|
5073
|
+
//#region src/approval-io.d.ts
|
|
5074
|
+
/**
|
|
5075
|
+
* The file operations approval maintenance and the unsent report are made of (design 2026-10-05 §D9), one method
|
|
5076
|
+
* each, so that a test can count every one of them — every directory read, metadata stat, record open, lock try,
|
|
5077
|
+
* renewal, ownership check and unlink — and none is hidden behind a helper. The store passes the real ones unless it
|
|
5078
|
+
* was opened with others.
|
|
5079
|
+
*
|
|
5080
|
+
* Only these two passes go through it: every other read and write of the store is as it was.
|
|
5081
|
+
*/
|
|
5082
|
+
interface ApprovalIo {
|
|
5083
|
+
/** One enumeration of a directory: its entries' names. */
|
|
5084
|
+
readdir(directory: string): Promise<string[]>;
|
|
5085
|
+
/** One metadata stat: the modification time, in milliseconds. */
|
|
5086
|
+
stat(path: string): Promise<{
|
|
5087
|
+
readonly mtimeMs: number;
|
|
5088
|
+
}>;
|
|
5089
|
+
/** One open of a file's content, as text. */
|
|
5090
|
+
readText(path: string): Promise<string>;
|
|
5091
|
+
/** Replaces a file atomically — a temporary file, flushed, renamed — and, when `durable`, syncs its directory too. */
|
|
5092
|
+
writeAtomic(path: string, text: string, options?: {
|
|
5093
|
+
durable?: boolean;
|
|
5094
|
+
}): Promise<void>;
|
|
5095
|
+
/** One unlink of an approval artifact: a record or its claim marker. A missing one rejects with `ENOENT`. */
|
|
5096
|
+
unlink(path: string): Promise<void>;
|
|
5097
|
+
/** The lock files' own operations (`tryFileLock`). */
|
|
5098
|
+
readonly lock: LockIo;
|
|
5099
|
+
}
|
|
5100
|
+
/** A version-1 record, as 0.13.0 wrote it: a send has no `kind`, and one `digest` stands for the content. */
|
|
5101
|
+
interface LegacyApprovalRecord {
|
|
5102
|
+
approvalId: string;
|
|
5103
|
+
/** Absent on a send. */
|
|
5104
|
+
kind?: ApprovalKind | undefined;
|
|
5105
|
+
digestVersion: 1;
|
|
5106
|
+
inboxId: string;
|
|
5107
|
+
inboxSub?: string | undefined;
|
|
5108
|
+
draftId: string;
|
|
5109
|
+
draftMessageId: string;
|
|
5110
|
+
digest: string;
|
|
5111
|
+
approvedDigest?: string | undefined;
|
|
5112
|
+
approvedVia?: ApprovalChannel | undefined;
|
|
5113
|
+
policy: SendPolicy;
|
|
5114
|
+
requiredPolicy: SendPolicy;
|
|
5115
|
+
riskFlags: string[];
|
|
5116
|
+
expect: Expectation;
|
|
5117
|
+
challengeHash?: string | undefined;
|
|
5118
|
+
challengeAttempts: number;
|
|
5119
|
+
state: ApprovalState;
|
|
5120
|
+
createdAt: string;
|
|
5121
|
+
expiresAt: string;
|
|
5122
|
+
updatedAt: string;
|
|
5123
|
+
sentMessageId?: string | undefined;
|
|
5124
|
+
reason?: string | undefined;
|
|
5125
|
+
change?: ChangeBinding | undefined;
|
|
5126
|
+
download?: DownloadBinding | undefined;
|
|
5127
|
+
}
|
|
5128
|
+
/**
|
|
5129
|
+
* A version-1 record as every surface shows it: `{ approvalId, kind, state, legacy: true, createdAt, expiresAt }` and
|
|
5130
|
+
* the summary a version-2 record shows — its owner, what it was for, and which channel made it when that can be told.
|
|
5131
|
+
* Never claimable, and never given a version-2 timestamp it does not hold.
|
|
5132
|
+
*/
|
|
5133
|
+
interface LegacyView {
|
|
5134
|
+
readonly approvalId: string;
|
|
5135
|
+
readonly kind: ApprovalKind;
|
|
5136
|
+
/** Derived as 0.13.0 derived it (`deriveLegacyV1State`). */
|
|
5137
|
+
readonly state: ApprovalState;
|
|
5138
|
+
readonly legacy: true;
|
|
5139
|
+
readonly createdAt: string;
|
|
5140
|
+
readonly expiresAt: string;
|
|
5141
|
+
/** The stored owner, trusted as stored: a version-1 record has no binding to check it against. */
|
|
5142
|
+
readonly inboxId: string;
|
|
5143
|
+
readonly ownerScope: "owner" | "global";
|
|
5144
|
+
/**
|
|
5145
|
+
* The channel that made it, when only one could have: an `ibx_` owner is a Gmail mailbox, an `acc_` owner the
|
|
5146
|
+
* stored platform of an account that still exists. Null when nothing says — it then takes part in no channel's
|
|
5147
|
+
* grouping and gets only generic wording.
|
|
5148
|
+
*/
|
|
5149
|
+
readonly channel: string | null;
|
|
5150
|
+
readonly expect: Expectation;
|
|
5151
|
+
readonly reason?: string | undefined;
|
|
5152
|
+
readonly sentMessageId?: string | undefined;
|
|
5153
|
+
}
|
|
5154
|
+
//#endregion
|
|
5155
|
+
//#region src/audit.d.ts
|
|
5156
|
+
/**
|
|
5157
|
+
* Every mailbox write leaves one line here: what was done, to which inbox, with which ids and outcome. Never message
|
|
5158
|
+
* bodies, never secrets — recipient *domains* only, so the log can be shared when asking for help.
|
|
5159
|
+
*/
|
|
5160
|
+
interface AuditRecord {
|
|
5161
|
+
at: string;
|
|
5162
|
+
/** Immutable inbox id; `alias` is the label at the time. */
|
|
5163
|
+
inboxId: string;
|
|
5164
|
+
alias?: string;
|
|
5165
|
+
operation: string;
|
|
5166
|
+
/** `started` marks the first half of a change recorded before it is made; a later record for the same ids ends it. */
|
|
5167
|
+
outcome: "ok" | "refused" | "failed" | "started";
|
|
5168
|
+
ids?: Record<string, string | string[] | CondensedIds>;
|
|
5169
|
+
recipientDomains?: string[];
|
|
5170
|
+
/**
|
|
5171
|
+
* Full canonical recipient addresses. Only a send writes these: for every other operation the domains are enough,
|
|
5172
|
+
* and storing less is better — but when mail has actually left, "who did it go to" is the first question anyone
|
|
5173
|
+
* asks afterwards, and a domain does not answer it.
|
|
5174
|
+
*/
|
|
5175
|
+
recipients?: string[];
|
|
5176
|
+
approvalId?: string;
|
|
5177
|
+
reason?: string;
|
|
5178
|
+
surface?: "cli" | "mcp";
|
|
5179
|
+
/**
|
|
5180
|
+
* The policy that decided how this was approved. Written by change approvals, where it is the whole question
|
|
5181
|
+
* afterwards: a loosening a person typed a code for and one an agent claimed after a yes in chat look the same in
|
|
5182
|
+
* the configuration, and only this line tells them apart.
|
|
5183
|
+
*/
|
|
5184
|
+
policy?: SendPolicy;
|
|
5185
|
+
/**
|
|
5186
|
+
* What daily retention deleted (`operation: 'approval.retained'`, design 2026-10-05 §D9): the approval's kind, its
|
|
5187
|
+
* final state, when it finished and any provider id — never its content or a recipient. Appended durably before the
|
|
5188
|
+
* record is unlinked, so the audit log keeps the terminal history the approval directory no longer does.
|
|
5189
|
+
*/
|
|
5190
|
+
retained?: RetainedApproval;
|
|
5191
|
+
}
|
|
5192
|
+
/** Large id lists are condensed so every audit line stays small enough to be appended atomically. */
|
|
5193
|
+
interface CondensedIds {
|
|
5194
|
+
count: number;
|
|
5195
|
+
sha256: string;
|
|
5196
|
+
first: string[];
|
|
5197
|
+
}
|
|
5198
|
+
declare class AuditLog {
|
|
5199
|
+
#private;
|
|
5200
|
+
readonly directory: string;
|
|
5201
|
+
constructor(stateDir: string, now?: () => Date);
|
|
5202
|
+
append(record: Omit<AuditRecord, "at"> & {
|
|
5203
|
+
at?: string;
|
|
5204
|
+
}, options?: {
|
|
5205
|
+
durable?: boolean;
|
|
5206
|
+
}): Promise<AuditRecord>;
|
|
5207
|
+
/** The most recent records, newest last, optionally filtered by inbox and a lower time bound. */
|
|
5208
|
+
tail(options?: {
|
|
5209
|
+
limit?: number;
|
|
5210
|
+
inbox?: string;
|
|
5211
|
+
since?: string;
|
|
5212
|
+
}): Promise<AuditRecord[]>;
|
|
5213
|
+
}
|
|
5214
|
+
/** The step of a batch that failed. */
|
|
5215
|
+
type MaintenanceStep = "lock" | "state" | "enumerate" | "stat" | "read" | "write" | "append" | "claim-unlink" | "record-unlink" | "lock-lost" | "cursor";
|
|
5216
|
+
/** A failure a batch met: which step, for which record, and the error's code — never anything a record holds. */
|
|
5217
|
+
interface MaintenanceError {
|
|
5218
|
+
readonly step: MaintenanceStep;
|
|
5219
|
+
readonly approvalId?: string | undefined;
|
|
5220
|
+
readonly code: string;
|
|
5221
|
+
}
|
|
5222
|
+
/**
|
|
5223
|
+
* What one call of `ensurePruned` did. `attempted` — it ran a batch; when it did not, `skipped` says why: not due
|
|
5224
|
+
* (a batch ran less than a day ago), busy (another process holds the maintenance lock), or no records (there is no
|
|
5225
|
+
* approvals directory at all). `processed` counts the slots whose record work ran to its end, `pruned` the records
|
|
5226
|
+
* deleted, `skippedBusy` the records whose lock was held elsewhere and were left. `complete` — the batch reached the end
|
|
5227
|
+
* of the directory inside both bounds; hitting 200 slots or the deadline, or losing the lock, leaves it false.
|
|
5228
|
+
*/
|
|
5229
|
+
interface MaintenanceStatus {
|
|
5230
|
+
readonly attempted: boolean;
|
|
5231
|
+
readonly processed: number;
|
|
5232
|
+
readonly pruned: number;
|
|
5233
|
+
readonly skippedBusy: number;
|
|
5234
|
+
readonly complete: boolean;
|
|
5235
|
+
readonly errors: readonly MaintenanceError[];
|
|
5236
|
+
readonly skipped?: "not-due" | "busy" | "no-records" | undefined;
|
|
5237
|
+
}
|
|
5238
|
+
/** What a batch reports of a record it is about to delete: never a body, an address, or anything a sender wrote. */
|
|
5239
|
+
interface RetainedApproval {
|
|
5240
|
+
readonly kind: ApprovalKind;
|
|
5241
|
+
readonly state: ApprovalState;
|
|
5242
|
+
/** When it finished: its validated terminal time, or a legacy used record's safe `updatedAt`. */
|
|
5243
|
+
readonly finishedAt: string;
|
|
5244
|
+
/** A used send's provider id, when it has a non-empty one. */
|
|
5245
|
+
readonly providerId?: string | undefined;
|
|
5246
|
+
/** A record an earlier release wrote. */
|
|
5247
|
+
readonly legacy?: true | undefined;
|
|
5248
|
+
}
|
|
5249
|
+
interface MaintenanceOptions {
|
|
5250
|
+
/**
|
|
5251
|
+
* When no further step may start, on the store's clock, in milliseconds: five seconds from the call when left out.
|
|
5252
|
+
* A report passes the one deadline it shares with its own reads.
|
|
5253
|
+
*/
|
|
5254
|
+
readonly deadline?: number | undefined;
|
|
5255
|
+
}
|
|
5256
|
+
//#endregion
|
|
5257
|
+
//#region src/approval-validate.d.ts
|
|
5258
|
+
/**
|
|
5259
|
+
* The integrity check of a version-2 approval record (design 2026-10-05 §D1, "Digest integrity" and "Version-2
|
|
5260
|
+
* timestamps fail closed"). Pure: it reads the record and the id its file is named by, and decides.
|
|
5261
|
+
*
|
|
5262
|
+
* A record either passes, or is `corrupt` for one fixed reason — never a sentence built from the record, so nothing a
|
|
5263
|
+
* file holds is ever echoed — and of one of two attribution classes. `verified`: its `bindingDigest` recomputes, so its
|
|
5264
|
+
* owner and identity can be trusted and it is corrupt for another reason (a state, a timestamp, its evidence).
|
|
5265
|
+
* `unverifiable`: its binding is missing, malformed or does not match, or it sits in another record's file, so nothing
|
|
5266
|
+
* it says about whom it belongs to can be trusted.
|
|
5267
|
+
*
|
|
5268
|
+
* Nothing here is wired into a read yet; the store's decoder does that.
|
|
5269
|
+
*/
|
|
5270
|
+
/** Why a version-2 record is corrupt: a fixed category, never the record's own words. */
|
|
5271
|
+
type IntegrityReason = "file-name-mismatch" | "binding-missing" | "binding-malformed" | "binding-mismatch" | "wrong-shape" | "digest-version" | "content-digest-malformed" | "content-digest-mismatch" | "identity-contradiction" | "listing-mismatch" | "state-impossible" | "lifetime-mismatch" | "timestamp-invalid" | "timestamp-missing" | "timestamp-misplaced" | "timestamp-misordered" | "sent-id-missing" | "evidence-missing" | "evidence-invalid" | "evidence-contradictory";
|
|
5272
|
+
type Attribution = "verified" | "unverifiable";
|
|
5273
|
+
//#endregion
|
|
5274
|
+
//#region src/approval-stored.d.ts
|
|
5275
|
+
/**
|
|
5276
|
+
* Every approval file, read one way (design 2026-10-05 §D2, "Legacy version-1 records, one decoder everywhere" and
|
|
5277
|
+
* "Two classes of parseable corrupt record").
|
|
5278
|
+
*
|
|
5279
|
+
* A file is one of four things, and every read says which:
|
|
5280
|
+
*
|
|
5281
|
+
* - `v2` — a version-2 record that passed `validateV2`: the only form anything may act on;
|
|
5282
|
+
* - `legacy` — a structurally valid version-1 record, read by its own release's rules: reported, never claimed;
|
|
5283
|
+
* - `corrupt` — a parseable version-2 record that failed its integrity check, attributable to its owner or not;
|
|
5284
|
+
* - `unreadable` — a file that cannot safely be read at all: shown only as a stub naming its id (from the file name)
|
|
5285
|
+
* and a fixed reason, never anything the file holds.
|
|
5286
|
+
*
|
|
5287
|
+
* Nothing is silently omitted, and the decoder writes nothing.
|
|
5288
|
+
*/
|
|
5289
|
+
/** Why a file cannot safely be read: a fixed category, never the file's own bytes. */
|
|
5290
|
+
type UnreadableReason = "invalid-json" | "truncated" | "missing-ownership" | "missing-kind" | "wrong-shape" | "file-name-mismatch";
|
|
5291
|
+
/** What may be said of a record that cannot be trusted: its id, that it is corrupt, and why — nothing else. */
|
|
5292
|
+
interface CorruptStub {
|
|
5293
|
+
readonly approvalId: string;
|
|
5294
|
+
readonly state: "corrupt";
|
|
5295
|
+
readonly reason: UnreadableReason | IntegrityReason;
|
|
5296
|
+
}
|
|
5297
|
+
/** What an attribution-verified corrupt record may still show its owner: the fields its binding was made over. */
|
|
5298
|
+
interface SafeFields {
|
|
5299
|
+
readonly kind: ApprovalKind;
|
|
5300
|
+
readonly channel: string;
|
|
5301
|
+
readonly ownerScope: OwnerScope;
|
|
5302
|
+
readonly inboxId: string;
|
|
5303
|
+
readonly inboxSub?: string | undefined;
|
|
5304
|
+
readonly draftId: string;
|
|
5305
|
+
readonly draftMessageId: string;
|
|
5306
|
+
readonly expect: Expectation;
|
|
5307
|
+
}
|
|
5308
|
+
type StoredApproval = {
|
|
5309
|
+
readonly form: "v2";
|
|
5310
|
+
readonly record: ApprovalRecord;
|
|
5311
|
+
} | {
|
|
5312
|
+
readonly form: "legacy";
|
|
5313
|
+
readonly view: LegacyView;
|
|
5314
|
+
readonly record: LegacyApprovalRecord;
|
|
5315
|
+
} | {
|
|
5316
|
+
readonly form: "corrupt";
|
|
5317
|
+
readonly approvalId: string;
|
|
5318
|
+
readonly reason: IntegrityReason;
|
|
5319
|
+
readonly attribution: Attribution;
|
|
5320
|
+
/** Only when attribution is verified. */
|
|
5321
|
+
readonly safe: SafeFields | null;
|
|
5322
|
+
} | {
|
|
5323
|
+
readonly form: "unreadable";
|
|
5324
|
+
readonly stub: CorruptStub;
|
|
5325
|
+
};
|
|
5326
|
+
//#endregion
|
|
5327
|
+
//#region src/approval-outcome.d.ts
|
|
5328
|
+
/**
|
|
5329
|
+
* One locked classification of every record into an outcome (design 2026-10-05 §D2).
|
|
5330
|
+
*
|
|
5331
|
+
* `approvalOutcome` says what a record is now — its state, whether it can be claimed, and what an action on it would be
|
|
5332
|
+
* told — from the record as it was read under its own lock and the configuration read in that same lock (`LiveGate`).
|
|
5333
|
+
* Nothing here reads a file or a clock of its own, and nothing here writes: the store persists what the outcome
|
|
5334
|
+
* derives, and only from inside the lock it was classified under.
|
|
5335
|
+
*/
|
|
5336
|
+
/** What is being done with the record: an approval, a claim, a wait, a revoke, or a look. */
|
|
5337
|
+
type OutcomeAction = "approve" | "claim" | "wait" | "revoke" | "inspect";
|
|
5338
|
+
/** A record's state as anyone may be told it: a download's answered question says `answered`. */
|
|
5339
|
+
type PublicApprovalState = ApprovalState | "corrupt" | "answered";
|
|
5340
|
+
/**
|
|
5341
|
+
* Where an approval stands, as every surface may show it (design 2026-10-05 §D8): its id, kind, channel, state and
|
|
5342
|
+
* whether it can be claimed, with the timestamps its state carries. Never a challenge hash, and nothing a sender wrote.
|
|
5343
|
+
*/
|
|
5344
|
+
interface ApprovalObject {
|
|
5345
|
+
readonly id: string;
|
|
5346
|
+
readonly kind: ApprovalKind | null;
|
|
5347
|
+
readonly channel: string | null;
|
|
5348
|
+
readonly state: PublicApprovalState;
|
|
5349
|
+
readonly claimable: boolean;
|
|
5350
|
+
readonly route?: ApprovalRoute | undefined;
|
|
5351
|
+
readonly ownerRemoved?: true | undefined;
|
|
5352
|
+
/** A record from an earlier release, read by its own rules: shown, never claimed. */
|
|
5353
|
+
readonly legacy?: true | undefined;
|
|
5354
|
+
readonly reason?: string | undefined;
|
|
5355
|
+
readonly createdAt?: string | undefined;
|
|
5356
|
+
readonly expiresAt?: string | undefined;
|
|
5357
|
+
readonly approvedAt?: string | undefined;
|
|
5358
|
+
readonly usableUntil?: string | undefined;
|
|
5359
|
+
readonly sendingAt?: string | undefined;
|
|
5360
|
+
readonly sendingHeartbeatAt?: string | undefined;
|
|
5361
|
+
/** When a `sending` record reads `unknown` (`unknownAtOf`): derived, never stored. Also on an `unknown` one. */
|
|
5362
|
+
readonly unknownAt?: string | undefined;
|
|
5363
|
+
readonly usedAt?: string | undefined;
|
|
5364
|
+
readonly sentAt?: string | undefined;
|
|
5365
|
+
readonly sentMessageId?: string | undefined;
|
|
5366
|
+
readonly failedAt?: string | undefined;
|
|
5367
|
+
readonly revokedAt?: string | undefined;
|
|
5368
|
+
readonly expiredAt?: string | undefined;
|
|
5369
|
+
}
|
|
5370
|
+
/** What an action on a record is, now. */
|
|
5371
|
+
interface ApprovalOutcome {
|
|
5372
|
+
readonly state: PublicApprovalState;
|
|
5373
|
+
readonly claimable: boolean;
|
|
5374
|
+
readonly ownerRemoved?: true | undefined;
|
|
5375
|
+
readonly reason?: string | undefined;
|
|
5376
|
+
/**
|
|
5377
|
+
* A version-2 record as this classification reads it: with a revocation it derived applied (an owner removed, a
|
|
5378
|
+
* stale send epoch, or — for a claim or an approval — a live `never`). Null for every other form.
|
|
5379
|
+
*/
|
|
5380
|
+
readonly record: ApprovalRecord | null;
|
|
5381
|
+
/** Whether `record` carries a revocation the classification derived, for a locked action to persist. */
|
|
5382
|
+
readonly revokes: boolean;
|
|
5383
|
+
/** Where it stands, as D8 shows it (`publicApproval`). */
|
|
5384
|
+
readonly approval: ApprovalObject;
|
|
5385
|
+
/** What the action is refused with, when it is refused by the record's state alone. */
|
|
5386
|
+
readonly error?: CommsError | undefined;
|
|
5387
|
+
}
|
|
5388
|
+
/**
|
|
5389
|
+
* What became of a used send, in the words of the surface that says it, from the time the provider accepted it (D2's
|
|
5390
|
+
* used-send row): Gmail and Slack, where acceptance is sending, say it was "sent at …"; Resend's own surfaces that it
|
|
5391
|
+
* was "accepted by Resend at …"; core's generic surfaces "accepted by <provider> at …".
|
|
5392
|
+
*/
|
|
5393
|
+
type UsedSaid = (usedAt: string) => string;
|
|
4931
5394
|
/**
|
|
4932
5395
|
* Why a saved name has {@link DOWNLOAD_SUFFIX} after it: a name tools read or run on their own (`auto-read`), an
|
|
4933
5396
|
* extension that is not one of the inert ones (`type`), or no extension at all (`no-extension`).
|
|
@@ -5077,19 +5540,54 @@ interface Expectation {
|
|
|
5077
5540
|
}
|
|
5078
5541
|
interface ApprovalRecord {
|
|
5079
5542
|
approvalId: string;
|
|
5080
|
-
/**
|
|
5081
|
-
kind
|
|
5543
|
+
/** What it permits. Every version-2 record says, a send included; only a version-1 send was written without one. */
|
|
5544
|
+
kind: ApprovalKind;
|
|
5082
5545
|
digestVersion: number;
|
|
5546
|
+
/** The manifest channel that made it (`gmail`, `slack`, `resend`, `whatsapp`, `core`), checked at creation. Bound. */
|
|
5547
|
+
channel: string;
|
|
5548
|
+
/** What `inboxId` names: an owner that existed, one the change creates, or none at all. Bound. */
|
|
5549
|
+
ownerScope: OwnerScope;
|
|
5550
|
+
/** A send's or a change's route, fixed at creation: a yes in the chat, or a person outside it. Bound. */
|
|
5551
|
+
route?: ApprovalRoute | undefined;
|
|
5552
|
+
/** How long it stays pending, from creation. Bound. */
|
|
5553
|
+
pendingMs: number;
|
|
5554
|
+
/** A send's or a change's lifetime once approved. Bound. */
|
|
5555
|
+
approvedMs?: number | undefined;
|
|
5083
5556
|
/** For a change: the id of the inbox or account it is about, or empty for one to the whole configuration. */
|
|
5084
5557
|
inboxId: string;
|
|
5085
5558
|
inboxSub?: string | undefined;
|
|
5086
5559
|
draftId: string;
|
|
5087
5560
|
/** Changes on every save of the draft; binding to it detects any edit, even one that restores identical content. */
|
|
5088
5561
|
draftMessageId: string;
|
|
5089
|
-
|
|
5090
|
-
|
|
5562
|
+
/** The outward content or change: the message, change or download digest. */
|
|
5563
|
+
contentDigest: string;
|
|
5564
|
+
/** The SHA-256 of everything the record binds (`bindingDigestOf`), recomputed from its own fields on every read. */
|
|
5565
|
+
bindingDigest: string;
|
|
5566
|
+
/** On a send: the owner's send epoch as it was read at prepare. Bound. */
|
|
5567
|
+
sendEpoch?: number | undefined;
|
|
5568
|
+
/**
|
|
5569
|
+
* A download's answer, bound: SHA-256 of canonical `{ bindingDigest, answer }`, written when a person answered at a
|
|
5570
|
+
* terminal or in a trusted form. Never on a send or a change, which carry `approvedBindingDigest` instead.
|
|
5571
|
+
*/
|
|
5091
5572
|
approvedDigest?: string | undefined;
|
|
5573
|
+
/** Where a person approved, or answered: the terminal, or a trusted client's form. Absent on a chat-route claim. */
|
|
5092
5574
|
approvedVia?: ApprovalChannel | undefined;
|
|
5575
|
+
/** A send's or a change's approval: when, and the binding the person approved, which must still be the record's. */
|
|
5576
|
+
approvedAt?: string | undefined;
|
|
5577
|
+
/** `approvedAt + approvedMs`: until when an approved send or change may be claimed. */
|
|
5578
|
+
usableUntil?: string | undefined;
|
|
5579
|
+
approvedBindingDigest?: string | undefined;
|
|
5580
|
+
/** When a send was claimed for sending. Kept on every state after it. */
|
|
5581
|
+
sendingAt?: string | undefined;
|
|
5582
|
+
/** The sending claimant's latest lease renewal, when it has made one. */
|
|
5583
|
+
sendingHeartbeatAt?: string | undefined;
|
|
5584
|
+
/** When a record was used: for a send, the moment the provider accepted it, and equal to `sentAt`. */
|
|
5585
|
+
usedAt?: string | undefined;
|
|
5586
|
+
sentAt?: string | undefined;
|
|
5587
|
+
failedAt?: string | undefined;
|
|
5588
|
+
revokedAt?: string | undefined;
|
|
5589
|
+
/** The boundary that applied when it expired — or, on a `clock-anomaly` expiry, the time the clock was seen. */
|
|
5590
|
+
expiredAt?: string | undefined;
|
|
5093
5591
|
/** Live policy at prepare time, for display and audit. */
|
|
5094
5592
|
policy: SendPolicy;
|
|
5095
5593
|
/** `confirm` when risk escalation raised this send. The effective policy is the stricter of this and the live one. */
|
|
@@ -5105,23 +5603,29 @@ interface ApprovalRecord {
|
|
|
5105
5603
|
updatedAt: string;
|
|
5106
5604
|
sentMessageId?: string | undefined;
|
|
5107
5605
|
reason?: string | undefined;
|
|
5108
|
-
/** For a change: exactly what it permits. `
|
|
5606
|
+
/** For a change: exactly what it permits. `contentDigest` is `changeDigest` of this. */
|
|
5109
5607
|
change?: ChangeBinding | undefined;
|
|
5110
|
-
/** For a download's question: what it asked about, and the folders it offered. `
|
|
5608
|
+
/** For a download's question: what it asked about, and the folders it offered. `contentDigest` is `downloadDigest`. */
|
|
5111
5609
|
download?: DownloadBinding | undefined;
|
|
5112
5610
|
}
|
|
5113
5611
|
interface CreateApprovalInput {
|
|
5612
|
+
/** The manifest channel preparing the send. A channel this release does not know is refused. */
|
|
5613
|
+
channel: string;
|
|
5114
5614
|
inboxId: string;
|
|
5115
5615
|
inboxSub?: string | undefined;
|
|
5116
5616
|
draftId: string;
|
|
5117
5617
|
draftMessageId: string;
|
|
5118
|
-
|
|
5618
|
+
contentDigest: string;
|
|
5619
|
+
/** The owner's send epoch, read from the configuration at prepare (`sendEpochOf`). */
|
|
5620
|
+
sendEpoch: number;
|
|
5119
5621
|
policy: SendPolicy;
|
|
5120
5622
|
requiredPolicy: SendPolicy;
|
|
5121
5623
|
riskFlags: string[];
|
|
5122
5624
|
expect: Expectation;
|
|
5123
5625
|
}
|
|
5124
5626
|
interface CreateChangeApprovalInput {
|
|
5627
|
+
/** The manifest channel of the surface preparing the change: `core` for core's own. */
|
|
5628
|
+
channel: string;
|
|
5125
5629
|
change: ChangeBinding;
|
|
5126
5630
|
/** The change policy in force before the change: it decides how the change is approved. */
|
|
5127
5631
|
policy: ChangePolicy;
|
|
@@ -5134,7 +5638,7 @@ interface LiveChange {
|
|
|
5134
5638
|
/** What the caller observed in the live draft at the moment of a transition. */
|
|
5135
5639
|
interface LiveDraft {
|
|
5136
5640
|
draftMessageId: string;
|
|
5137
|
-
|
|
5641
|
+
contentDigest: string;
|
|
5138
5642
|
}
|
|
5139
5643
|
/** What the product making a claim tells the store about itself. */
|
|
5140
5644
|
interface ClaimOptions {
|
|
@@ -5154,6 +5658,11 @@ interface ClaimOptions {
|
|
|
5154
5658
|
* names no product at all rather than the wrong one.
|
|
5155
5659
|
*/
|
|
5156
5660
|
pendingHint?: string | undefined;
|
|
5661
|
+
/**
|
|
5662
|
+
* How a used send is said in the refusal of a claim of it — the surface's own words, as `pendingHint` is (design
|
|
5663
|
+
* 2026-10-05 §D2's used-send row): "sent at …" when left out, as Gmail and Slack say it.
|
|
5664
|
+
*/
|
|
5665
|
+
usedSaid?: UsedSaid | undefined;
|
|
5157
5666
|
/**
|
|
5158
5667
|
* The caller's cancellation — an MCP request's signal — asked under the record's lock, immediately before the claim
|
|
5159
5668
|
* would change the record.
|
|
@@ -5166,19 +5675,124 @@ interface ClaimOptions {
|
|
|
5166
5675
|
*/
|
|
5167
5676
|
signal?: AbortSignal | undefined;
|
|
5168
5677
|
}
|
|
5678
|
+
/** What a send's claim gives its claimant: the record, and the private token only the claimant holds (`heartbeat`, `fence`, `complete`). */
|
|
5679
|
+
interface SendClaim {
|
|
5680
|
+
readonly record: ApprovalRecord;
|
|
5681
|
+
/**
|
|
5682
|
+
* 16 random bytes, hex: kept only in the claim's `O_EXCL` marker beside the record, and handed only to the call
|
|
5683
|
+
* that claimed. Never in a record, a view, an approval object, an audit row, an error or a result.
|
|
5684
|
+
*/
|
|
5685
|
+
readonly claimToken: string;
|
|
5686
|
+
/** Where the approval stands now that it is claimed (design 2026-10-05 §D8): `sending`, never claimable. */
|
|
5687
|
+
readonly approval: ApprovalObject;
|
|
5688
|
+
}
|
|
5689
|
+
/** A send's outcome, as only its claimant records it. */
|
|
5690
|
+
type SendOutcome = {
|
|
5691
|
+
readonly sentMessageId: string;
|
|
5692
|
+
} | {
|
|
5693
|
+
readonly error: string;
|
|
5694
|
+
};
|
|
5695
|
+
/**
|
|
5696
|
+
* Whose decision a revoke carries, which decides what it may do to a record from an earlier release (version 1).
|
|
5697
|
+
*
|
|
5698
|
+
* `person` — a person cancelled it, from a tool, a command or a terminal. `lifecycle` — the owner it belongs to is being
|
|
5699
|
+
* removed, which a person approved. Either retires a version-1 record in its own shape (`revokeLegacy`), so a running
|
|
5700
|
+
* earlier release can no longer claim it. `integrity` — this release found the record does not match what it binds:
|
|
5701
|
+
* that never rewrites a version-1 record, which this release cannot judge by its own rules, and gets the version
|
|
5702
|
+
* refusal instead. For a version-2 record all three revoke alike.
|
|
5703
|
+
*/
|
|
5704
|
+
type RevokeDisposition = "person" | "lifecycle" | "integrity";
|
|
5705
|
+
/**
|
|
5706
|
+
* What a caller takes an id to be: an approval of `kind` (any kind when left out); on a channel's own surface that is
|
|
5707
|
+
* no one owner's — its terminal approval — one of `channel`'s, the manifest channel that prepared it; and on a surface
|
|
5708
|
+
* pinned to one mailbox or account, or a claim made for one, owned by `owner`. Anything else is the one `NOT_FOUND`
|
|
5709
|
+
* (`approvalNotFound`), checked before the record's state is looked at: another channel's send given to Gmail's
|
|
5710
|
+
* `approve` is not found there, as one nobody prepared is not (design 2026-10-05 §D2).
|
|
5711
|
+
*/
|
|
5712
|
+
interface ApprovalExpectation {
|
|
5713
|
+
readonly kind?: ApprovalKind | undefined;
|
|
5714
|
+
readonly channel?: string | undefined;
|
|
5715
|
+
readonly owner?: string | undefined;
|
|
5716
|
+
}
|
|
5717
|
+
/** How the store is opened. */
|
|
5718
|
+
interface ApprovalStoreOptions {
|
|
5719
|
+
now?: () => Date;
|
|
5720
|
+
handoffs?: CliHandoffs | undefined;
|
|
5721
|
+
/**
|
|
5722
|
+
* The configuration, read afresh: the one thing a read of the store consults beyond the files themselves — to
|
|
5723
|
+
* attribute a version-1 record whose owner is an account to that account's platform, and for every locked
|
|
5724
|
+
* transition, the live gate it classifies by (`liveGateOf`): the owner, its policies and its send epoch. Read once
|
|
5725
|
+
* per `get`, once per `list`, and once per locked transition after its lock is held, so one call decodes and
|
|
5726
|
+
* classifies against one snapshot. Its errors propagate: a configuration that cannot be read is never taken for none.
|
|
5727
|
+
* Resolving `null`, or left out, is "no configuration": a version-1 account record is then unattributable, nothing is
|
|
5728
|
+
* ever claimable, and a claim, an approval or an answer is refused with `CONFIG`.
|
|
5729
|
+
*/
|
|
5730
|
+
loadConfig?: (() => Promise<Config$1 | null>) | undefined;
|
|
5731
|
+
/**
|
|
5732
|
+
* The audit log daily retention records each deletion in (`approval.retained`, design 2026-10-05 §D9): the state
|
|
5733
|
+
* directory's own when left out. `openCore` passes the one it opens.
|
|
5734
|
+
*/
|
|
5735
|
+
audit?: Pick<AuditLog, "append"> | undefined;
|
|
5736
|
+
/**
|
|
5737
|
+
* The file operations daily maintenance and the unsent report go through — the real ones when left out; a test's to
|
|
5738
|
+
* count and interrupt every step. Every other read and write of the store is unaffected.
|
|
5739
|
+
*/
|
|
5740
|
+
io?: ApprovalIo | undefined;
|
|
5741
|
+
}
|
|
5169
5742
|
declare class ApprovalStore {
|
|
5170
5743
|
#private;
|
|
5171
5744
|
readonly directory: string;
|
|
5172
|
-
constructor(stateDir: string, options?:
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5745
|
+
constructor(stateDir: string, options?: ApprovalStoreOptions);
|
|
5746
|
+
/**
|
|
5747
|
+
* Daily retention (design 2026-10-05 §D9): once a day per state directory, a bounded batch deletes valid finished
|
|
5748
|
+
* records ninety days past their finish. Awaited before every creation, list, status and wait — with its own
|
|
5749
|
+
* five-second budget — and before a report, on the report's shared deadline. Never a gate: whatever it finds, it
|
|
5750
|
+
* returns its status and the caller goes on.
|
|
5751
|
+
*/
|
|
5752
|
+
ensurePruned(options?: MaintenanceOptions): Promise<MaintenanceStatus>;
|
|
5178
5753
|
create(input: CreateApprovalInput): Promise<ApprovalRecord>;
|
|
5179
|
-
|
|
5754
|
+
/**
|
|
5755
|
+
* One approval, in whichever of its four forms it is (`StoredApproval`), read against one configuration snapshot —
|
|
5756
|
+
* or null when there is none. Nothing is written.
|
|
5757
|
+
*/
|
|
5758
|
+
get(approvalId: string): Promise<StoredApproval | null>;
|
|
5759
|
+
/**
|
|
5760
|
+
* A look at one approval, under its lock: the record as it stands and its classification for `expect` — the same
|
|
5761
|
+
* `NOT_FOUND` as every action for an id that is not what the caller expects, before anything of the record is
|
|
5762
|
+
* classified. It writes only what reading derives (an expiry, a dead send's `unknown`), never a revocation: an owner
|
|
5763
|
+
* removed or a stale epoch is shown as revoked here, and written by the next action.
|
|
5764
|
+
*
|
|
5765
|
+
* `action` is for a surface about to act on what it reads — a terminal about to show a preview, an execute about to
|
|
5766
|
+
* read its draft — classified for that action (`approve`, `claim`, `revoke`), so the refusal is the one the action
|
|
5767
|
+
* would get. A revocation the classification derives for it is written then, as the action itself would write it:
|
|
5768
|
+
* the first locked action persists it, and this is that action's first step (design 2026-10-05 §D2).
|
|
5769
|
+
*/
|
|
5770
|
+
inspect(approvalId: string, expect?: ApprovalExpectation, options?: {
|
|
5771
|
+
action?: OutcomeAction | undefined;
|
|
5772
|
+
usedSaid?: UsedSaid | undefined;
|
|
5773
|
+
}): Promise<{
|
|
5774
|
+
stored: StoredApproval;
|
|
5775
|
+
outcome: ApprovalOutcome;
|
|
5776
|
+
}>;
|
|
5777
|
+
/**
|
|
5778
|
+
* D8's object for a record a call has just written, claimed or finished — what its result reports. Classified as a
|
|
5779
|
+
* look at it would be, against a fresh read of the configuration, without the lock and writing nothing: what decided
|
|
5780
|
+
* was the transition that wrote it. A configuration that cannot be read leaves only what the record says (nothing
|
|
5781
|
+
* claimable): a report never fails the call it reports on, least of all one that has already sent.
|
|
5782
|
+
*/
|
|
5783
|
+
approvalOf(record: ApprovalRecord): Promise<ApprovalObject>;
|
|
5784
|
+
/**
|
|
5785
|
+
* The classification of a record a call has just written, in any of its forms — a revoke's, a cancel's — for its
|
|
5786
|
+
* result to show through `publicApproval`, as a status, a wait and a list show theirs: what a sender wrote only inside
|
|
5787
|
+
* the untrusted-content envelope (design 2026-10-05 §D8). Classified as a look at it would be, against a fresh read of
|
|
5788
|
+
* the configuration, without the lock and writing nothing, as `approvalOf` is; a configuration that cannot be read
|
|
5789
|
+
* leaves nothing claimable, and never fails the call it reports on.
|
|
5790
|
+
*/
|
|
5791
|
+
outcomeOf(stored: StoredApproval): Promise<ApprovalOutcome>;
|
|
5180
5792
|
/** Issues a new challenge to show a human; only its hash is kept. */
|
|
5181
|
-
issueChallenge(approvalId: string, kind?: ApprovalKind,
|
|
5793
|
+
issueChallenge(approvalId: string, kind?: ApprovalKind, _platform?: NodeJS.Platform, options?: {
|
|
5794
|
+
usedSaid?: UsedSaid | undefined;
|
|
5795
|
+
}): Promise<string>;
|
|
5182
5796
|
/**
|
|
5183
5797
|
* A human approved through a confirm channel by typing the issued challenge. The draft must still be exactly what the
|
|
5184
5798
|
* record was prepared for; otherwise the record is voided, because the human would be approving content the record
|
|
@@ -5187,18 +5801,35 @@ declare class ApprovalStore {
|
|
|
5187
5801
|
* A change is approved the same way, with `kind: 'change'` and its digest standing in for the draft (see
|
|
5188
5802
|
* `createChange`), so a person's typed code means one thing whichever kind of approval it is typed for.
|
|
5189
5803
|
*/
|
|
5190
|
-
approve(approvalId: string, via: ApprovalChannel, live: LiveDraft, answer: string, kind?: ApprovalKind,
|
|
5804
|
+
approve(approvalId: string, via: ApprovalChannel, live: LiveDraft, answer: string, kind?: ApprovalKind, _platform?: NodeJS.Platform, options?: {
|
|
5805
|
+
usedSaid?: UsedSaid | undefined;
|
|
5806
|
+
}): Promise<ApprovalRecord>;
|
|
5191
5807
|
/**
|
|
5192
5808
|
* Claims the record for sending, once. Non-consuming refusals (not yet approved) leave the record untouched so the
|
|
5193
|
-
* human can still approve it; integrity failures (
|
|
5194
|
-
*
|
|
5809
|
+
* human can still approve it; integrity failures (another account, an edited or changed draft, different recipients
|
|
5810
|
+
* or subject) void it. Success creates the O_EXCL claim marker.
|
|
5811
|
+
*
|
|
5812
|
+
* `live.inboxId` is the owner the caller claims for: a record of another owner is the one `NOT_FOUND`, and left as it
|
|
5813
|
+
* is. The policy is the live gate's, from the configuration read under the record's lock (decision 4): a value the
|
|
5814
|
+
* caller read before the lock is exactly what a `never` change can land between.
|
|
5195
5815
|
*/
|
|
5196
5816
|
claimForSend(approvalId: string, live: LiveDraft & {
|
|
5197
5817
|
inboxId: string;
|
|
5198
5818
|
inboxSub?: string | undefined;
|
|
5199
|
-
policy: SendPolicy;
|
|
5200
5819
|
expect: Expectation;
|
|
5201
|
-
}, options?: ClaimOptions): Promise<
|
|
5820
|
+
}, options?: ClaimOptions): Promise<SendClaim>;
|
|
5821
|
+
/**
|
|
5822
|
+
* Renews the sending lease of a send this call claimed: writes `sendingHeartbeatAt` while the claim still owns a
|
|
5823
|
+
* `sending` record, and nothing otherwise. `renewed` while it is sending; `lost` once it reads `unknown` (or anything
|
|
5824
|
+
* else) — a renewal never moves a record back to `sending`. Refused, writing nothing, without the claim's token.
|
|
5825
|
+
*/
|
|
5826
|
+
heartbeat(approvalId: string, claimToken: string): Promise<"renewed" | "lost">;
|
|
5827
|
+
/**
|
|
5828
|
+
* The check before each provider step (design 2026-10-05 §D1): `go`, with the lease renewed, while this claim still
|
|
5829
|
+
* owns a `sending` record; `stop` once it reads `unknown` — another caller has decided its outcome cannot be known,
|
|
5830
|
+
* and no further step may start. Refused, writing nothing, without the claim's token.
|
|
5831
|
+
*/
|
|
5832
|
+
fence(approvalId: string, claimToken: string): Promise<"go" | "stop">;
|
|
5202
5833
|
/**
|
|
5203
5834
|
* A change approval, pending, bound to `changeDigest(input.change)`.
|
|
5204
5835
|
*
|
|
@@ -5229,6 +5860,8 @@ declare class ApprovalStore {
|
|
|
5229
5860
|
* other changes are approved. A claim holds the question to the stricter of this and the policy then.
|
|
5230
5861
|
*/
|
|
5231
5862
|
createDownload(input: {
|
|
5863
|
+
/** The manifest channel the files come from. */
|
|
5864
|
+
channel: string;
|
|
5232
5865
|
download: DownloadBinding;
|
|
5233
5866
|
policy: ChangePolicy;
|
|
5234
5867
|
}): Promise<ApprovalRecord & {
|
|
@@ -5241,7 +5874,7 @@ declare class ApprovalStore {
|
|
|
5241
5874
|
*
|
|
5242
5875
|
* Only a pending question can be answered, and only once: a second answer is refused, not taken over the first.
|
|
5243
5876
|
*/
|
|
5244
|
-
answerDownload(approvalId: string, via: ApprovalChannel, answer: RecordedSaveAnswer,
|
|
5877
|
+
answerDownload(approvalId: string, via: ApprovalChannel, answer: RecordedSaveAnswer, _platform?: NodeJS.Platform): Promise<ApprovalRecord & {
|
|
5245
5878
|
download: DownloadBinding;
|
|
5246
5879
|
}>;
|
|
5247
5880
|
/**
|
|
@@ -5254,17 +5887,17 @@ declare class ApprovalStore {
|
|
|
5254
5887
|
* open, as it was: a second call that got an argument wrong is the agent's slip, not the person's, and voiding the
|
|
5255
5888
|
* question for it would make the person answer again for nothing. It still expires, and is claimed once.
|
|
5256
5889
|
*
|
|
5257
|
-
*
|
|
5258
|
-
*
|
|
5259
|
-
*
|
|
5260
|
-
*
|
|
5261
|
-
*
|
|
5890
|
+
* Whether it can be claimed is the download `claimable` matrix (`downloadClaimable`, design 2026-10-05 §D2), decided
|
|
5891
|
+
* under the question's lock against the live change policy read there — never a policy the caller read before it.
|
|
5892
|
+
* While both the policy the question was asked under and the live one are `chat`, a pending question is claimed
|
|
5893
|
+
* with the answer the caller carries: the person gave it in the conversation, and it is never kept. Otherwise only a
|
|
5894
|
+
* question the person answered at a terminal or in a trusted form can be claimed; a pending one is refused, and left
|
|
5895
|
+
* as it is, so they can still answer it. One used, voided, expired or corrupt is refused for what it is.
|
|
5262
5896
|
*
|
|
5263
5897
|
* `options.signal` is the download's cancellation, asked under the lock as a send's is (`ClaimOptions.signal`): a
|
|
5264
5898
|
* download cancelled while this waited saves nothing and leaves the question open, the answer still unused.
|
|
5265
5899
|
*/
|
|
5266
5900
|
claimForDownload(approvalId: string, live: DownloadRequest, options?: {
|
|
5267
|
-
policy?: ChangePolicy | undefined;
|
|
5268
5901
|
pendingHint?: string | undefined;
|
|
5269
5902
|
signal?: AbortSignal | undefined;
|
|
5270
5903
|
platform?: NodeJS.Platform | undefined;
|
|
@@ -5272,72 +5905,76 @@ declare class ApprovalStore {
|
|
|
5272
5905
|
}): Promise<ApprovalRecord & {
|
|
5273
5906
|
download: DownloadBinding;
|
|
5274
5907
|
}>;
|
|
5275
|
-
/** Records the outcome of the one send attempt. */
|
|
5276
|
-
complete(approvalId: string, outcome: {
|
|
5277
|
-
sentMessageId: string;
|
|
5278
|
-
} | {
|
|
5279
|
-
error: string;
|
|
5280
|
-
}): Promise<ApprovalRecord>;
|
|
5281
|
-
/** Voids a pending or approved record (user revoked it, or policy tightened). Other records are left as they are. */
|
|
5282
|
-
revoke(approvalId: string, reason: string): Promise<ApprovalRecord>;
|
|
5283
|
-
list(filter?: {
|
|
5284
|
-
inboxId?: string;
|
|
5285
|
-
states?: ApprovalState[];
|
|
5286
|
-
}): Promise<ApprovalRecord[]>;
|
|
5287
|
-
}
|
|
5288
|
-
//#endregion
|
|
5289
|
-
//#region src/audit.d.ts
|
|
5290
|
-
/**
|
|
5291
|
-
* Every mailbox write leaves one line here: what was done, to which inbox, with which ids and outcome. Never message
|
|
5292
|
-
* bodies, never secrets — recipient *domains* only, so the log can be shared when asking for help.
|
|
5293
|
-
*/
|
|
5294
|
-
interface AuditRecord {
|
|
5295
|
-
at: string;
|
|
5296
|
-
/** Immutable inbox id; `alias` is the label at the time. */
|
|
5297
|
-
inboxId: string;
|
|
5298
|
-
alias?: string;
|
|
5299
|
-
operation: string;
|
|
5300
|
-
/** `started` marks the first half of a change recorded before it is made; a later record for the same ids ends it. */
|
|
5301
|
-
outcome: "ok" | "refused" | "failed" | "started";
|
|
5302
|
-
ids?: Record<string, string | string[] | CondensedIds>;
|
|
5303
|
-
recipientDomains?: string[];
|
|
5304
5908
|
/**
|
|
5305
|
-
*
|
|
5306
|
-
*
|
|
5307
|
-
*
|
|
5909
|
+
* Records the outcome of the one send attempt: `used` with the provider's id for it, when the provider accepted it,
|
|
5910
|
+
* or `failed`.
|
|
5911
|
+
*
|
|
5912
|
+
* `used` needs a non-empty id. A provider that accepted a send without one has not given anything to record it by,
|
|
5913
|
+
* so the record is left as it is — `sending`, and then `unknown` — rather than made `used` with an empty id that
|
|
5914
|
+
* every later reader would take for one.
|
|
5915
|
+
*
|
|
5916
|
+
* Only the claimant records it, by its claim token, from `sending` or from `unknown`: a provider's answer that
|
|
5917
|
+
* arrives after the lease ran out is still the truth. A late success clears the stale-lease reason; a late failure
|
|
5918
|
+
* replaces it with its own.
|
|
5308
5919
|
*/
|
|
5309
|
-
|
|
5310
|
-
approvalId?: string;
|
|
5311
|
-
reason?: string;
|
|
5312
|
-
surface?: "cli" | "mcp";
|
|
5920
|
+
complete(approvalId: string, claimToken: string, outcome: SendOutcome): Promise<ApprovalRecord>;
|
|
5313
5921
|
/**
|
|
5314
|
-
*
|
|
5315
|
-
*
|
|
5316
|
-
*
|
|
5922
|
+
* Voids a pending or approved record. Other records are left as they are, and returned as they read.
|
|
5923
|
+
*
|
|
5924
|
+
* `disposition` says whose decision this is, and has no default: every caller chooses (`RevokeDisposition`). A
|
|
5925
|
+
* version-1 record is retired in its own shape when a person or an owner's removal revokes it (`revokeLegacy`), and
|
|
5926
|
+
* refused with the version refusal, nothing written, when this release's integrity check would have voided it. A
|
|
5927
|
+
* corrupt or unreadable record is never revoked or rewritten: it is refused with the integrity refusal, which repeats
|
|
5928
|
+
* only its stub.
|
|
5929
|
+
*/
|
|
5930
|
+
revoke(approvalId: string, reason: string, options: {
|
|
5931
|
+
disposition: RevokeDisposition;
|
|
5932
|
+
expect?: ApprovalExpectation | undefined;
|
|
5933
|
+
}): Promise<StoredApproval>;
|
|
5934
|
+
/**
|
|
5935
|
+
* Retires a record from an earlier release (version 1) in that release's own shape, so a process still running it
|
|
5936
|
+
* can no longer claim the record — and nothing more.
|
|
5937
|
+
*
|
|
5938
|
+
* Under the record's own lock, `<id>.json.lock`, the one 0.13.0's transitions take, so the two serialise. The file
|
|
5939
|
+
* is read again there, decoded, and its state derived by its own release's rules (`deriveLegacyV1State`). Only a
|
|
5940
|
+
* `pending` or `approved` record is rewritten, as itself plus `state: 'revoked'`, the reason and `updatedAt`: its
|
|
5941
|
+
* digest version stays 1, a send stays without a `kind`, and no field of this release's is added — no claim marker
|
|
5942
|
+
* either. Any other derived state is returned as it reads and written nothing: an expired record is never rewritten,
|
|
5943
|
+
* as `revoke` leaves every finished record as it is.
|
|
5944
|
+
*
|
|
5945
|
+
* It reads no configuration and needs no owner, so it works on a record whose account is already gone. Its only
|
|
5946
|
+
* callers are a person's revoke, the removal of the record's owner, and the legacy drain.
|
|
5317
5947
|
*/
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5948
|
+
revokeLegacy(approvalId: string, reason: string): Promise<LegacyApprovalRecord>;
|
|
5949
|
+
/**
|
|
5950
|
+
* Every approval file, each looked at under its own lock and classified there, as `inspect` looks at one — against one
|
|
5951
|
+
* configuration snapshot, loaded once before any file is read (design 2026-10-05 §D2, §D8). What a list shows: each
|
|
5952
|
+
* record's form and its outcome, nothing skipped. Like a look, it writes only what reading derives — an expiry at its
|
|
5953
|
+
* boundary, a stale send's `unknown` — never a revocation: an owner removed or a stale epoch is shown as revoked, and
|
|
5954
|
+
* written by the next action.
|
|
5955
|
+
*
|
|
5956
|
+
* `inboxId` keeps the records whose owner is that id (`ownerOf`), so one whose owner cannot be trusted never matches;
|
|
5957
|
+
* `states` keeps those whose classified state is listed — a stored state, `corrupt`, and for a download's answered
|
|
5958
|
+
* question its stored `approved` or `used`.
|
|
5959
|
+
*/
|
|
5960
|
+
inspectAll(filter?: {
|
|
5961
|
+
inboxId?: string;
|
|
5962
|
+
states?: (ApprovalState | "corrupt")[];
|
|
5963
|
+
}): Promise<Array<{
|
|
5964
|
+
stored: StoredApproval;
|
|
5965
|
+
outcome: ApprovalOutcome;
|
|
5966
|
+
}>>;
|
|
5967
|
+
/**
|
|
5968
|
+
* Every approval file, each in one of its four forms — nothing skipped, nothing omitted — read against one
|
|
5969
|
+
* configuration snapshot, loaded once before any file is read: a loader that fails fails the whole list.
|
|
5970
|
+
*
|
|
5971
|
+
* `inboxId` keeps the records whose owner is that id (`ownerOf`), so a file whose owner cannot be trusted never
|
|
5972
|
+
* matches one; `states` keeps those whose state (`stateOf`, `corrupt` for one that cannot be used) is listed.
|
|
5973
|
+
*/
|
|
5974
|
+
list(filter?: {
|
|
5975
|
+
inboxId?: string;
|
|
5976
|
+
states?: (ApprovalState | "corrupt")[];
|
|
5977
|
+
}): Promise<StoredApproval[]>;
|
|
5341
5978
|
}
|
|
5342
5979
|
//#endregion
|
|
5343
5980
|
//#region src/ledger.d.ts
|
|
@@ -5501,9 +6138,23 @@ interface TaintExclusions {
|
|
|
5501
6138
|
*/
|
|
5502
6139
|
ownHandles?: readonly TaintHandle[] | undefined;
|
|
5503
6140
|
}
|
|
6141
|
+
/**
|
|
6142
|
+
* One stored aggregate, exactly as the store holds it (design 2026-10-05 §D4): the newest time anything matched it, the
|
|
6143
|
+
* strongest source ever seen — `header` is kept once seen — and every mailbox that recorded it. Three separate facts,
|
|
6144
|
+
* never one sighting: `touch` replaces the time, keeps a header and unions the mailboxes independently.
|
|
6145
|
+
*/
|
|
6146
|
+
interface TaintAggregate {
|
|
6147
|
+
readonly at: string;
|
|
6148
|
+
readonly source: TaintSource;
|
|
6149
|
+
readonly inboxIds: readonly string[];
|
|
6150
|
+
}
|
|
5504
6151
|
interface TaintCheck {
|
|
5505
6152
|
address: boolean;
|
|
5506
6153
|
domain: boolean;
|
|
6154
|
+
/** The exact address's aggregate, when it is in the window. */
|
|
6155
|
+
addressSeen?: TaintAggregate | undefined;
|
|
6156
|
+
/** Its domain's, when that counts — not a public mailbox provider — and is in the window. */
|
|
6157
|
+
domainSeen?: TaintAggregate | undefined;
|
|
5507
6158
|
}
|
|
5508
6159
|
declare class TaintStore {
|
|
5509
6160
|
#private;
|
|
@@ -5514,7 +6165,10 @@ declare class TaintStore {
|
|
|
5514
6165
|
* was not recorded.
|
|
5515
6166
|
*/
|
|
5516
6167
|
record(observations: readonly TaintObservation[], exclusions: TaintExclusions, handleObservations?: readonly TaintHandleObservation[]): Promise<void>;
|
|
5517
|
-
/**
|
|
6168
|
+
/**
|
|
6169
|
+
* Whether an address, or its (non-public) domain, was seen in email content in the window — from any inbox — and the
|
|
6170
|
+
* aggregate each match is made of, for a caller to explain it. Nothing is written.
|
|
6171
|
+
*/
|
|
5518
6172
|
check(address: string): Promise<TaintCheck>;
|
|
5519
6173
|
/**
|
|
5520
6174
|
* Whether a handle was seen in message content in the window.
|