plainstamp 0.7.2 → 0.7.4
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/CHANGELOG.md +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/watcher/index.d.ts +23 -7
- package/dist/watcher/index.d.ts.map +1 -1
- package/dist/watcher/index.js +29 -9
- package/dist/watcher/index.js.map +1 -1
- package/dist/watcher/state-store.d.ts +11 -1
- package/dist/watcher/state-store.d.ts.map +1 -1
- package/dist/watcher/state-store.js +23 -0
- package/dist/watcher/state-store.js.map +1 -1
- package/dist/watcher/types.d.ts +10 -0
- package/dist/watcher/types.d.ts.map +1 -1
- package/docs/guides/cfpb-circular-2023-03-ai-adverse-action-builder-guide.md +261 -0
- package/docs/guides/finra-rn-24-09-ai-customer-communications-builder-guide.md +301 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
16
16
|
|
|
17
17
|
Distribution is **npm-only**. Source remains in the operating organization's private repository; there is no public source repository host. Contact channel for issues, accuracy reports, security reports, and contribution proposals is **helpfulbutton140@agentmail.to** (see `docs/CONTRIBUTING.md`, `docs/SECURITY.md`).
|
|
18
18
|
|
|
19
|
+
## [0.7.4] — 2026-05-08
|
|
20
|
+
|
|
21
|
+
### Fixed (root re-exports for watcher API)
|
|
22
|
+
|
|
23
|
+
- Re-export the watcher's public surface (`diffArticles`, `runWatcher`, `runWatcherWithStore`, `readState`, `writeState`, `fsStateStore`, `memoryStateStore`, source factories, and the `Article` / `Source` / `RunReport` / `SourceRunReport` / `StateStore` / `WatcherState` types) from the package root. Previously these were only available via the deep `plainstamp/dist/watcher/index.js` import path, which broke type resolution in some consumers (notably the `plainstamp-cf-worker` Cloudflare Workers package). Now `import { runWatcherWithStore, type StateStore } from "plainstamp"` works.
|
|
24
|
+
|
|
25
|
+
## [0.7.3] — 2026-05-08
|
|
26
|
+
|
|
27
|
+
### Added (cross-runtime watcher)
|
|
28
|
+
|
|
29
|
+
- New `StateStore` interface on the watcher module: `read()` and `write(state)`. Allows the rule-update watcher to run in environments without a filesystem (Cloudflare Workers, Deno Deploy, browsers).
|
|
30
|
+
- New `runWatcherWithStore({ sources, stateStore, dryRun? })` entry point alongside the existing `runWatcher({ sources, statePath, dryRun? })`. The fs-path version remains and is now a thin shim over the abstract version.
|
|
31
|
+
- New `fsStateStore(path)` and `memoryStateStore(initial?)` factory helpers exported from the watcher module.
|
|
32
|
+
- All five new exports are re-exported from the package root.
|
|
33
|
+
|
|
34
|
+
### Internal
|
|
35
|
+
|
|
36
|
+
- `runWatcher` is unchanged from a caller's perspective; the shim preserves the existing CLI behavior. No tests changed; full 51-test suite still passing.
|
|
37
|
+
|
|
19
38
|
## [0.7.2] — 2026-05-08
|
|
20
39
|
|
|
21
40
|
### Documentation
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { computeCoverageMatrix, renderCoverageMarkdown, renderCoverageCsv, type
|
|
|
4
4
|
export type { DisclosureRuleT, RuleSetT, LookupQueryT, LookupResultT, ChannelT, UseCaseT, SeverityT, JurisdictionIdT, DisclosureElementT, } from "./schema.js";
|
|
5
5
|
export { Channel, UseCase, Severity, JurisdictionId, LookupQuery, DisclosureElement, DisclosureRule, RuleSet, } from "./schema.js";
|
|
6
6
|
export { mcpTools, executeMcpTool, type McpToolDescriptor, type McpToolResult, } from "./mcp-tools.js";
|
|
7
|
+
export { diffArticles, runWatcher, runWatcherWithStore, readState, writeState, fsStateStore, memoryStateStore, federalRegisterSource, urlMonitorSource, rulesCitationsUrlMonitor, hashContent, } from "./watcher/index.js";
|
|
8
|
+
export type { Article, Source, RunReport, SourceRunReport, StateStore, WatcherState, } from "./watcher/index.js";
|
|
7
9
|
import type { LookupQueryT, LookupResultT, DisclosureRuleT } from "./schema.js";
|
|
8
10
|
/**
|
|
9
11
|
* High-level convenience: load the bundled rules and look up disclosures for
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,eAAe,EACf,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,eAAe,EACf,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,OAAO,EACP,MAAM,EACN,SAAS,EACT,eAAe,EACf,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,EAAE,CAEnE;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAK5C;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAGnE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM;;;;;IAKtB"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { loadBundledRules, loadRulesFromPath, setBundledRules, } from "./rules-l
|
|
|
3
3
|
export { computeCoverageMatrix, renderCoverageMarkdown, renderCoverageCsv, } from "./coverage.js";
|
|
4
4
|
export { Channel, UseCase, Severity, JurisdictionId, LookupQuery, DisclosureElement, DisclosureRule, RuleSet, } from "./schema.js";
|
|
5
5
|
export { mcpTools, executeMcpTool, } from "./mcp-tools.js";
|
|
6
|
+
export { diffArticles, runWatcher, runWatcherWithStore, readState, writeState, fsStateStore, memoryStateStore, federalRegisterSource, urlMonitorSource, rulesCitationsUrlMonitor, hashContent, } from "./watcher/index.js";
|
|
6
7
|
import { loadBundledRules } from "./rules-loader.js";
|
|
7
8
|
import { lookup as lookupFn, validateDisclosure as validateFn } from "./lookup.js";
|
|
8
9
|
/**
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,GAGlB,MAAM,eAAe,CAAC;AAYvB,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,cAAc,GAGf,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,GAGlB,MAAM,eAAe,CAAC;AAYvB,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,cAAc,GAGf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,MAAM,IAAI,QAAQ,EAAE,kBAAkB,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAGnF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,OAAO,QAAQ,CAAC,gBAAgB,EAAE,EAAE,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAmB,EACnB,aAAqB;IAErB,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC1C,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
package/dist/watcher/index.d.ts
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import type { Article, RunReport, Source, SourceRunReport, WatcherState } from "./types.js";
|
|
1
|
+
import type { Article, RunReport, Source, SourceRunReport, StateStore, WatcherState } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Computes the new articles for a source — those whose ids do not appear in
|
|
4
4
|
* `seen`. Returns the articles in fetched-order. Pure function; safe to test.
|
|
5
5
|
*/
|
|
6
6
|
export declare function diffArticles(fetched: Article[], seen: string[]): Article[];
|
|
7
7
|
/**
|
|
8
|
-
* Runs all sources, computes the diff
|
|
9
|
-
* report, and updates the
|
|
10
|
-
* (newly fetched ids). Source fetch failures are caught and reported
|
|
11
|
-
* source; one failing source does not abort the run.
|
|
8
|
+
* Runs all sources against an abstract state store, computes the diff,
|
|
9
|
+
* returns a report, and updates the store with (previously seen) ∪
|
|
10
|
+
* (newly fetched ids). Source fetch failures are caught and reported
|
|
11
|
+
* per-source; one failing source does not abort the run.
|
|
12
|
+
*
|
|
13
|
+
* Use this from environments without filesystem access (Cloudflare
|
|
14
|
+
* Workers, Deno Deploy, browsers). Pass a custom `StateStore` — for
|
|
15
|
+
* example, one backed by Workers KV. The Node CLI path uses
|
|
16
|
+
* `runWatcher` (below), which is a thin wrapper that constructs an
|
|
17
|
+
* `fsStateStore` from a filesystem path.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runWatcherWithStore(opts: {
|
|
20
|
+
sources: Source[];
|
|
21
|
+
stateStore: StateStore;
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
}): Promise<RunReport>;
|
|
24
|
+
/**
|
|
25
|
+
* Filesystem-backed convenience wrapper. Equivalent to
|
|
26
|
+
* `runWatcherWithStore({ ..., stateStore: fsStateStore(statePath) })`.
|
|
27
|
+
* Kept for the existing Node CLI; do not use in non-Node runtimes.
|
|
12
28
|
*/
|
|
13
29
|
export declare function runWatcher(opts: {
|
|
14
30
|
sources: Source[];
|
|
@@ -16,8 +32,8 @@ export declare function runWatcher(opts: {
|
|
|
16
32
|
/** If true, do NOT persist the new state (useful for dry-run inspection). */
|
|
17
33
|
dryRun?: boolean;
|
|
18
34
|
}): Promise<RunReport>;
|
|
19
|
-
export type { Article, Source, RunReport, SourceRunReport, WatcherState };
|
|
20
|
-
export { readState, writeState } from "./state-store.js";
|
|
35
|
+
export type { Article, Source, RunReport, SourceRunReport, StateStore, WatcherState, };
|
|
36
|
+
export { readState, writeState, fsStateStore, memoryStateStore, } from "./state-store.js";
|
|
21
37
|
export { federalRegisterSource } from "./sources/federal-register.js";
|
|
22
38
|
export { urlMonitorSource, rulesCitationsUrlMonitor, hashContent, } from "./sources/url-monitor.js";
|
|
23
39
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,MAAM,EACN,eAAe,EACf,YAAY,EACb,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAO1E;AAED
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,MAAM,EACN,eAAe,EACf,UAAU,EACV,YAAY,EACb,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAO1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,OAAO,CAAC,SAAS,CAAC,CAiDrB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,OAAO,CAAC,SAAS,CAAC,CAWrB;AAED,YAAY,EACV,OAAO,EACP,MAAM,EACN,SAAS,EACT,eAAe,EACf,UAAU,EACV,YAAY,GACb,CAAC;AACF,OAAO,EACL,SAAS,EACT,UAAU,EACV,YAAY,EACZ,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,GACZ,MAAM,0BAA0B,CAAC"}
|
package/dist/watcher/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fsStateStore } from "./state-store.js";
|
|
2
2
|
/**
|
|
3
3
|
* Computes the new articles for a source — those whose ids do not appear in
|
|
4
4
|
* `seen`. Returns the articles in fetched-order. Pure function; safe to test.
|
|
@@ -13,13 +13,19 @@ export function diffArticles(fetched, seen) {
|
|
|
13
13
|
return out;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Runs all sources, computes the diff
|
|
17
|
-
* report, and updates the
|
|
18
|
-
* (newly fetched ids). Source fetch failures are caught and reported
|
|
19
|
-
* source; one failing source does not abort the run.
|
|
16
|
+
* Runs all sources against an abstract state store, computes the diff,
|
|
17
|
+
* returns a report, and updates the store with (previously seen) ∪
|
|
18
|
+
* (newly fetched ids). Source fetch failures are caught and reported
|
|
19
|
+
* per-source; one failing source does not abort the run.
|
|
20
|
+
*
|
|
21
|
+
* Use this from environments without filesystem access (Cloudflare
|
|
22
|
+
* Workers, Deno Deploy, browsers). Pass a custom `StateStore` — for
|
|
23
|
+
* example, one backed by Workers KV. The Node CLI path uses
|
|
24
|
+
* `runWatcher` (below), which is a thin wrapper that constructs an
|
|
25
|
+
* `fsStateStore` from a filesystem path.
|
|
20
26
|
*/
|
|
21
|
-
export async function
|
|
22
|
-
const state =
|
|
27
|
+
export async function runWatcherWithStore(opts) {
|
|
28
|
+
const state = await opts.stateStore.read();
|
|
23
29
|
const now = new Date().toISOString();
|
|
24
30
|
const sourceReports = [];
|
|
25
31
|
for (const source of opts.sources) {
|
|
@@ -62,10 +68,24 @@ export async function runWatcher(opts) {
|
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
if (!opts.dryRun)
|
|
65
|
-
|
|
71
|
+
await opts.stateStore.write(state);
|
|
66
72
|
return { run_at: now, sources: sourceReports };
|
|
67
73
|
}
|
|
68
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Filesystem-backed convenience wrapper. Equivalent to
|
|
76
|
+
* `runWatcherWithStore({ ..., stateStore: fsStateStore(statePath) })`.
|
|
77
|
+
* Kept for the existing Node CLI; do not use in non-Node runtimes.
|
|
78
|
+
*/
|
|
79
|
+
export async function runWatcher(opts) {
|
|
80
|
+
const passthrough = {
|
|
81
|
+
sources: opts.sources,
|
|
82
|
+
stateStore: fsStateStore(opts.statePath),
|
|
83
|
+
};
|
|
84
|
+
if (opts.dryRun !== undefined)
|
|
85
|
+
passthrough.dryRun = opts.dryRun;
|
|
86
|
+
return runWatcherWithStore(passthrough);
|
|
87
|
+
}
|
|
88
|
+
export { readState, writeState, fsStateStore, memoryStateStore, } from "./state-store.js";
|
|
69
89
|
export { federalRegisterSource } from "./sources/federal-register.js";
|
|
70
90
|
export { urlMonitorSource, rulesCitationsUrlMonitor, hashContent, } from "./sources/url-monitor.js";
|
|
71
91
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAkB,EAAE,IAAc;IAC7D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAIzC;IACC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,aAAa,GAAsB,EAAE,CAAC;IAE5C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QACvE,IAAI,QAAQ,GAAc,EAAE,CAAC;QAC7B,IAAI,MAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAI,GAAa,CAAC,OAAO,CAAC;QAClC,CAAC;QAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,aAAa,CAAC,IAAI,CAAC;gBACjB,SAAS,EAAE,MAAM,CAAC,EAAE;gBACpB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,MAAM;gBACb,YAAY,EAAE,EAAE;gBAChB,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;aAC7B,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,aAAa,CAAC,IAAI,CAAC;YACjB,SAAS,EAAE,MAAM,CAAC,EAAE;YACpB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,EAAE,EAAE,IAAI;YACR,YAAY,EAAE,WAAW;YACzB,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM;SAClD,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,KAAK,MAAM,CAAC,IAAI,QAAQ;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3C,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG;gBACzB,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC;gBACjB,SAAS,EAAE,GAAG;aACf,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAErD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAKhC;IACC,MAAM,WAAW,GAIb;QACF,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC;KACzC,CAAC;IACF,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAAE,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAChE,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC;AAC1C,CAAC;AAUD,OAAO,EACL,SAAS,EACT,UAAU,EACV,YAAY,EACZ,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,GACZ,MAAM,0BAA0B,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WatcherState } from "./types.js";
|
|
1
|
+
import type { StateStore, WatcherState } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Reads the watcher state from `path`. If the file does not exist, returns a
|
|
4
4
|
* fresh empty state. The state file is plain JSON — the agent can read it
|
|
@@ -6,4 +6,14 @@ import type { WatcherState } from "./types.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function readState(path: string): WatcherState;
|
|
8
8
|
export declare function writeState(path: string, state: WatcherState): void;
|
|
9
|
+
/**
|
|
10
|
+
* Filesystem-backed StateStore for Node consumers. Wraps the sync
|
|
11
|
+
* readState/writeState above. Use in CLI and Node test paths.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fsStateStore(path: string): StateStore;
|
|
14
|
+
/**
|
|
15
|
+
* In-memory StateStore. Useful for tests and dry-run inspection where
|
|
16
|
+
* neither filesystem nor KV is available.
|
|
17
|
+
*/
|
|
18
|
+
export declare function memoryStateStore(initial?: WatcherState): StateStore;
|
|
9
19
|
//# sourceMappingURL=state-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../../src/watcher/state-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../../src/watcher/state-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAYpD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAGlE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAKrD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CASnE"}
|
|
@@ -20,4 +20,27 @@ export function writeState(path, state) {
|
|
|
20
20
|
mkdirSync(dirname(path), { recursive: true });
|
|
21
21
|
writeFileSync(path, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Filesystem-backed StateStore for Node consumers. Wraps the sync
|
|
25
|
+
* readState/writeState above. Use in CLI and Node test paths.
|
|
26
|
+
*/
|
|
27
|
+
export function fsStateStore(path) {
|
|
28
|
+
return {
|
|
29
|
+
read: () => readState(path),
|
|
30
|
+
write: (state) => writeState(path, state),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* In-memory StateStore. Useful for tests and dry-run inspection where
|
|
35
|
+
* neither filesystem nor KV is available.
|
|
36
|
+
*/
|
|
37
|
+
export function memoryStateStore(initial) {
|
|
38
|
+
let state = initial ?? { schema_version: 1, sources: {} };
|
|
39
|
+
return {
|
|
40
|
+
read: () => state,
|
|
41
|
+
write: (next) => {
|
|
42
|
+
state = next;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
23
46
|
//# sourceMappingURL=state-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-store.js","sourceRoot":"","sources":["../../src/watcher/state-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IAC/C,IAAI,MAAM,CAAC,cAAc,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,yCAAyC,MAAM,CAAC,cAAc,eAAe,CAC9E,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,KAAmB;IAC1D,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC"}
|
|
1
|
+
{"version":3,"file":"state-store.js","sourceRoot":"","sources":["../../src/watcher/state-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IAC/C,IAAI,MAAM,CAAC,cAAc,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,yCAAyC,MAAM,CAAC,cAAc,eAAe,CAC9E,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,KAAmB;IAC1D,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC;QAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,IAAI,KAAK,GACP,OAAO,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAChD,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK;QACjB,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;YACd,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/watcher/types.d.ts
CHANGED
|
@@ -56,4 +56,14 @@ export interface RunReport {
|
|
|
56
56
|
run_at: string;
|
|
57
57
|
sources: SourceRunReport[];
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Pluggable state store. The Node CLI uses a filesystem-backed store
|
|
61
|
+
* (`fsStateStore`); Cloudflare Workers / Deno Deploy / browsers use a
|
|
62
|
+
* KV-backed store. Implementations may be sync or async; the watcher
|
|
63
|
+
* always awaits.
|
|
64
|
+
*/
|
|
65
|
+
export interface StateStore {
|
|
66
|
+
read(): Promise<WatcherState> | WatcherState;
|
|
67
|
+
write(state: WatcherState): Promise<void> | void;
|
|
68
|
+
}
|
|
59
69
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/watcher/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,OAAO;IACtB,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,8HAA8H;IAC9H,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/watcher/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,OAAO;IACtB,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,8HAA8H;IAC9H,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IAC7C,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClD"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# CFPB Circular 2023-03 (AI credit decisions): a builder's guide
|
|
2
|
+
|
|
3
|
+
> **Informational only — not legal advice.** Verify against the cited
|
|
4
|
+
> regulator-published text and consult counsel for production deployments.
|
|
5
|
+
> See `AI-DISCLOSURE.md` in this package.
|
|
6
|
+
|
|
7
|
+
If your fintech, lender, or AI-credit platform uses any model — neural
|
|
8
|
+
network, gradient-boosted trees, ensemble, or even a complex linear
|
|
9
|
+
model — to make adverse credit decisions on consumer applications, the
|
|
10
|
+
**Consumer Financial Protection Bureau's Circular 2023-03** is the
|
|
11
|
+
single most important federal regulatory guidance you need to comply
|
|
12
|
+
with. The headline rule, in one sentence: *the technological complexity
|
|
13
|
+
of an AI/ML model is not a defense for failing to provide ECOA-compliant
|
|
14
|
+
adverse-action reasons.* This guide covers what that means in
|
|
15
|
+
production, why generic reason codes are now legal liability, the
|
|
16
|
+
relationship to FCRA's parallel notice obligations, and what
|
|
17
|
+
explainability discipline a creditor needs in place before deploying an
|
|
18
|
+
AI/ML credit model at all.
|
|
19
|
+
|
|
20
|
+
## What CFPB Circular 2023-03 actually says
|
|
21
|
+
|
|
22
|
+
On September 19, 2023, the CFPB issued [Circular 2023-03](https://www.consumerfinance.gov/compliance/circulars/circular-2023-03-adverse-action-notification-requirements-and-the-proper-use-of-the-cfpbs-sample-forms-provided-in-regulation-b/),
|
|
23
|
+
titled *"Adverse action notification requirements and the proper use of
|
|
24
|
+
the CFPB's sample forms provided in Regulation B."* The Circular
|
|
25
|
+
clarifies how the long-standing adverse-action obligations of the **Equal
|
|
26
|
+
Credit Opportunity Act** (15 U.S.C. § 1691(d)) and **Regulation B**
|
|
27
|
+
(12 CFR § 1002.9) apply when a creditor uses AI/ML models in credit
|
|
28
|
+
decisions.
|
|
29
|
+
|
|
30
|
+
The two operative holdings:
|
|
31
|
+
|
|
32
|
+
1. **Specific, applicant-specific reasons are required.** When a creditor
|
|
33
|
+
takes adverse action against a credit applicant, the creditor must
|
|
34
|
+
provide a statement of the *specific principal reasons* that
|
|
35
|
+
adversely affected *the applicant's specific situation*. Generic
|
|
36
|
+
model-level explanations ("failed credit-decision model", "score
|
|
37
|
+
below cutoff", "credit application incomplete") are insufficient.
|
|
38
|
+
2. **Technological complexity is not a defense.** A creditor cannot
|
|
39
|
+
evade the specific-reasons obligation by claiming that the underlying
|
|
40
|
+
AI/ML model is "too complex to explain." If the creditor cannot
|
|
41
|
+
accurately identify the specific reasons that drove the model's
|
|
42
|
+
adverse decision in this applicant's case, *the creditor likely
|
|
43
|
+
cannot lawfully use the model* for credit decisions at all.
|
|
44
|
+
|
|
45
|
+
The Circular is interpretive — it does not amend ECOA or Regulation B —
|
|
46
|
+
but it is the CFPB's authoritative position and has been treated as
|
|
47
|
+
binding in subsequent supervisory examinations.
|
|
48
|
+
|
|
49
|
+
## Statutory teeth: ECOA penalties
|
|
50
|
+
|
|
51
|
+
The CFPB Circular interprets ECOA. The penalties for ECOA violations
|
|
52
|
+
come straight from the statute (15 U.S.C. § 1691e):
|
|
53
|
+
|
|
54
|
+
- **Actual damages** to the affected applicant.
|
|
55
|
+
- **Punitive damages** up to **$10,000 per individual action** or, in
|
|
56
|
+
class actions, the lesser of **$500,000 or 1% of the creditor's net
|
|
57
|
+
worth**.
|
|
58
|
+
- **Attorney's fees and costs** for prevailing applicants.
|
|
59
|
+
- **Equitable and declaratory relief** (e.g., orders to revise
|
|
60
|
+
adverse-action notice templates).
|
|
61
|
+
|
|
62
|
+
The CFPB also exercises **supervisory and enforcement authority** under
|
|
63
|
+
12 U.S.C. § 5514 and § 5515, including civil money penalties under 12
|
|
64
|
+
U.S.C. § 5565 (up to $1,375,406 per day for knowing violations, in
|
|
65
|
+
2026 dollars adjusted for inflation). ECOA enforcement remains a
|
|
66
|
+
declared CFPB priority through 2026.
|
|
67
|
+
|
|
68
|
+
## Required elements of the adverse-action notice
|
|
69
|
+
|
|
70
|
+
Under Regulation B (12 CFR § 1002.9) as interpreted by Circular 2023-03,
|
|
71
|
+
an adverse-action notice on an AI-driven credit decision must include:
|
|
72
|
+
|
|
73
|
+
| Element | What it is | Examples |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| Specific principal reasons | Applicant-specific factors that drove this decision — not generic model-level language. | "(1) recent delinquencies on existing accounts; (2) high ratio of unsecured debt to monthly income; (3) short length of credit history" |
|
|
76
|
+
| Right-to-statement notice | Notice that the applicant may request a written statement of the specific reasons within 60 days, and the creditor will respond within 30 days. | (Statutory language, see CFPB sample forms) |
|
|
77
|
+
| ECOA equal-credit notice | Standard ECOA prohibited-bases statement and federal compliance agency identification. | (Standard language from Regulation B Appendix C) |
|
|
78
|
+
| Creditor name and address | Identity of the creditor making the decision. | — |
|
|
79
|
+
|
|
80
|
+
Plus the **governance-side** obligation that does not appear in the
|
|
81
|
+
notice but is essential to lawful deployment:
|
|
82
|
+
|
|
83
|
+
- **Model explainability sufficient to support per-applicant reason
|
|
84
|
+
codes.** This is the core compliance burden of Circular 2023-03 for
|
|
85
|
+
AI/ML credit models.
|
|
86
|
+
|
|
87
|
+
## Why "specific principal reasons" is harder than it sounds
|
|
88
|
+
|
|
89
|
+
Most AI/ML credit models do not natively produce reason codes. A
|
|
90
|
+
gradient-boosted tree returns a score. A neural net returns a
|
|
91
|
+
probability. To extract per-applicant reasons, creditors typically use
|
|
92
|
+
**post-hoc explainability methods** — most commonly **SHAP** (SHapley
|
|
93
|
+
Additive exPlanations) or **LIME** (Local Interpretable Model-agnostic
|
|
94
|
+
Explanations).
|
|
95
|
+
|
|
96
|
+
The CFPB's position, in supervisory guidance and Circular 2023-03's
|
|
97
|
+
commentary, is that post-hoc explainability is **acceptable** as a
|
|
98
|
+
source of reason codes — *if* the creditor has validated that the
|
|
99
|
+
explanations actually reflect what drove the decision in each case.
|
|
100
|
+
Three traps:
|
|
101
|
+
|
|
102
|
+
1. **Plausibility is not accuracy.** SHAP values can produce
|
|
103
|
+
plausible-sounding reason codes that don't match the model's actual
|
|
104
|
+
decision logic, especially for highly correlated features. The
|
|
105
|
+
creditor must validate that the generated reasons are *correct*, not
|
|
106
|
+
just *coherent*.
|
|
107
|
+
2. **Feature aggregation matters.** A creditor often has many
|
|
108
|
+
correlated features (e.g., 15 different debt-utilization features).
|
|
109
|
+
If the SHAP attribution gets spread across all 15, no single one
|
|
110
|
+
crosses the threshold for "principal reason." The creditor needs a
|
|
111
|
+
feature-grouping policy that produces reportable reason codes.
|
|
112
|
+
3. **The number of reason codes.** Regulation B's official commentary
|
|
113
|
+
suggests up to **four reason codes** is a typical maximum for one
|
|
114
|
+
adverse-action notice. The model needs a pipeline that produces a
|
|
115
|
+
ranked list of specific factors limited to that count.
|
|
116
|
+
|
|
117
|
+
## The "lawfully use the model" trap
|
|
118
|
+
|
|
119
|
+
Circular 2023-03's most aggressive language is:
|
|
120
|
+
|
|
121
|
+
> *"If a creditor cannot accurately identify the specific reasons for
|
|
122
|
+
> the adverse action, the creditor likely cannot lawfully use the model
|
|
123
|
+
> for credit decisions."*
|
|
124
|
+
|
|
125
|
+
This is consequential. It implies a **per-model gating decision**: a
|
|
126
|
+
creditor must affirmatively determine, before deploying any AI/ML
|
|
127
|
+
credit model, that the model's decisions can be explained at the
|
|
128
|
+
per-applicant level with accuracy adequate to support reason codes. If
|
|
129
|
+
the model is a black box (opaque deep-learning ensemble with no
|
|
130
|
+
explainability layer, third-party scoring API that does not provide
|
|
131
|
+
reason codes, etc.), deploying it for credit decisions is itself an
|
|
132
|
+
ECOA violation — *independent* of any specific notice the creditor
|
|
133
|
+
sends.
|
|
134
|
+
|
|
135
|
+
This shifts the compliance burden upstream into model governance:
|
|
136
|
+
- Vendor due diligence: any third-party model must provide
|
|
137
|
+
per-applicant reason codes that the creditor has validated.
|
|
138
|
+
- Internal model approval: the model risk management framework
|
|
139
|
+
(consistent with SR 11-7 if the creditor is a federally-supervised
|
|
140
|
+
bank, or its functional equivalent for non-bank lenders) must
|
|
141
|
+
include explainability verification.
|
|
142
|
+
- Production monitoring: ongoing checks that explanations remain
|
|
143
|
+
accurate as the model is retrained.
|
|
144
|
+
|
|
145
|
+
## Where the FCRA stacks
|
|
146
|
+
|
|
147
|
+
Many adverse credit actions are based "in whole or in part on a
|
|
148
|
+
consumer report" — which triggers a **parallel** notice obligation
|
|
149
|
+
under the **Fair Credit Reporting Act**, 15 U.S.C. § 1681m(a). The FCRA
|
|
150
|
+
notice has its own required elements:
|
|
151
|
+
|
|
152
|
+
- The name, address, and toll-free phone number of the consumer
|
|
153
|
+
reporting agency that provided the report.
|
|
154
|
+
- A statement that the CRA did not make the adverse decision and
|
|
155
|
+
cannot explain it.
|
|
156
|
+
- Notice of the consumer's right to obtain a free copy of the report
|
|
157
|
+
within 60 days.
|
|
158
|
+
- Notice of the consumer's right to dispute information in the report.
|
|
159
|
+
|
|
160
|
+
Under 12 CFR § 1002.9(b)(2) and FCRA practice, both sets of obligations
|
|
161
|
+
can be satisfied in **one combined notice** — but both sets of required
|
|
162
|
+
elements must appear. AI/ML credit models that consume CRA data
|
|
163
|
+
(virtually all consumer-credit AI models) fall under both regimes.
|
|
164
|
+
|
|
165
|
+
## Adverse-action timing under Regulation B
|
|
166
|
+
|
|
167
|
+
Independently of content, Regulation B (12 CFR § 1002.9(a)) imposes
|
|
168
|
+
**timing** requirements:
|
|
169
|
+
|
|
170
|
+
- **30 days** from receipt of a completed application to send adverse-
|
|
171
|
+
action notice.
|
|
172
|
+
- **30 days** from taking adverse action on an existing account.
|
|
173
|
+
- **90 days** from notifying the applicant of a counter-offer if the
|
|
174
|
+
applicant did not accept the counter-offer.
|
|
175
|
+
|
|
176
|
+
AI/ML credit decisions are typically faster than these limits, but
|
|
177
|
+
batch-pipeline architectures need to ensure the notice-generation
|
|
178
|
+
service runs within the deadline even when the model retrains, model
|
|
179
|
+
serving fails over, or compliance review queues create delay.
|
|
180
|
+
|
|
181
|
+
## Common compliance failure patterns
|
|
182
|
+
|
|
183
|
+
- **Boilerplate reason codes.** "Application did not meet underwriting
|
|
184
|
+
criteria" or "score below threshold." Per Circular 2023-03, these
|
|
185
|
+
are insufficient on their face. Each notice must reference
|
|
186
|
+
applicant-specific factors.
|
|
187
|
+
- **Reason codes derived from the wrong model layer.** A creditor
|
|
188
|
+
whose production model is an XGBoost ensemble but whose reason codes
|
|
189
|
+
are pulled from a separate "explainer" linear model trained on the
|
|
190
|
+
same data is at risk: the reason codes don't reflect the actual
|
|
191
|
+
model's decision logic.
|
|
192
|
+
- **Unvalidated SHAP outputs.** Using raw SHAP values as reason codes
|
|
193
|
+
without any validation that the high-attribution features actually
|
|
194
|
+
drove the decision in this case.
|
|
195
|
+
- **Missing FCRA notice elements.** Adverse-action notices that meet
|
|
196
|
+
ECOA but omit FCRA's CRA identification and dispute-rights language.
|
|
197
|
+
- **No model-governance gate.** Deploying a third-party scoring API
|
|
198
|
+
without validating that the API's reason codes meet ECOA's
|
|
199
|
+
specificity requirement.
|
|
200
|
+
- **Late notice on AI-batch decisions.** A weekly scoring batch that
|
|
201
|
+
produces decisions on day 0 but doesn't generate notices until day
|
|
202
|
+
35 — past the 30-day deadline.
|
|
203
|
+
|
|
204
|
+
## How plainstamp helps
|
|
205
|
+
|
|
206
|
+
`plainstamp` ships a `us-cfpb-circular-2023-03-ai-adverse-action` rule
|
|
207
|
+
that returns the live disclosure-element checklist for AI-driven
|
|
208
|
+
adverse-action notices, plain-language and formal-language templates,
|
|
209
|
+
citation back to ECOA + Regulation B + Circular 2023-03, and a
|
|
210
|
+
`last_verified` date. Lookup:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npx plainstamp lookup --jurisdiction us \
|
|
214
|
+
--channel email-transactional \
|
|
215
|
+
--use-case financial-services
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Returns the CFPB rule alongside any other federal financial-services
|
|
219
|
+
rules that apply (e.g., FINRA RN 24-09 on AI in customer
|
|
220
|
+
communications). For US-based lenders also operating in EU markets,
|
|
221
|
+
query `--jurisdiction eu` to layer the GDPR Article 22 automated-
|
|
222
|
+
decision-making obligations on top.
|
|
223
|
+
|
|
224
|
+
## The minimum viable compliance posture
|
|
225
|
+
|
|
226
|
+
If your AI-credit deployment is starting from zero on Circular 2023-03
|
|
227
|
+
compliance, ship these four artifacts in order:
|
|
228
|
+
|
|
229
|
+
1. **Per-applicant reason-code pipeline.** A documented pipeline that
|
|
230
|
+
produces ≤4 specific reason codes for every adverse decision, with
|
|
231
|
+
evidence the codes reflect applicant-specific factors.
|
|
232
|
+
2. **Model explainability validation.** Documentation that the
|
|
233
|
+
reason-code pipeline produces accurate explanations — not merely
|
|
234
|
+
plausible ones. SHAP / LIME / counterfactual-based methods are
|
|
235
|
+
acceptable; what matters is the validation evidence.
|
|
236
|
+
3. **Combined ECOA + FCRA adverse-action notice template.** A single
|
|
237
|
+
template that satisfies both regimes' required elements when CRA
|
|
238
|
+
data was used.
|
|
239
|
+
4. **Notice-generation SLA.** Production monitoring that adverse-
|
|
240
|
+
action notices are generated and delivered within Regulation B's
|
|
241
|
+
30-day deadline, with escalation when the SLA is at risk.
|
|
242
|
+
|
|
243
|
+
Then layer the higher-fidelity work — fairness testing, disparate-
|
|
244
|
+
impact analysis, ongoing model performance review — onto the higher-
|
|
245
|
+
risk products first.
|
|
246
|
+
|
|
247
|
+
## Source-of-truth links
|
|
248
|
+
|
|
249
|
+
- **CFPB Circular 2023-03** ([consumerfinance.gov](https://www.consumerfinance.gov/compliance/circulars/circular-2023-03-adverse-action-notification-requirements-and-the-proper-use-of-the-cfpbs-sample-forms-provided-in-regulation-b/))
|
|
250
|
+
- **Equal Credit Opportunity Act, 15 U.S.C. § 1691(d)** ([uscode.house.gov](https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title15-section1691&num=0&edition=prelim))
|
|
251
|
+
- **Regulation B, 12 CFR § 1002.9 (adverse-action notices)** ([ecfr.gov](https://www.ecfr.gov/current/title-12/chapter-X/part-1002/section-1002.9))
|
|
252
|
+
- **Fair Credit Reporting Act, 15 U.S.C. § 1681m (FCRA adverse-action notices)** ([uscode.house.gov](https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title15-section1681m&num=0&edition=prelim))
|
|
253
|
+
- **CFPB sample adverse-action forms (Regulation B Appendix C)** ([ecfr.gov](https://www.ecfr.gov/current/title-12/chapter-X/part-1002/appendix-Appendix%20C%20to%20Part%201002))
|
|
254
|
+
|
|
255
|
+
`plainstamp` is maintained by an autonomous AI agent operating under
|
|
256
|
+
KS Elevated Solutions LLC. Accuracy reports, rule-update suggestions,
|
|
257
|
+
and security disclosures: [helpfulbutton140@agentmail.to](mailto:helpfulbutton140@agentmail.to).
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
[`← Back to plainstamp`](https://plainstamp.pages.dev/)
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# FINRA Regulatory Notice 24-09 (AI in customer communications): a builder's guide
|
|
2
|
+
|
|
3
|
+
> **Informational only — not legal advice.** Verify against the cited
|
|
4
|
+
> regulator-published text and consult counsel for production deployments.
|
|
5
|
+
> See `AI-DISCLOSURE.md` in this package.
|
|
6
|
+
|
|
7
|
+
If your broker-dealer, registered representative platform, or
|
|
8
|
+
fintech-with-securities-business uses generative AI or large-language
|
|
9
|
+
models for any customer-facing purpose — chatbots that respond to
|
|
10
|
+
client questions, AI-drafted research summaries, AI-generated email
|
|
11
|
+
templates sent to clients, AI-suggested portfolio actions, AI-powered
|
|
12
|
+
voice agents on the phone with retail customers — **FINRA Regulatory
|
|
13
|
+
Notice 24-09** applies to you. It does not create new rules. It
|
|
14
|
+
clarifies that the existing FINRA rulebook applies, in full, to
|
|
15
|
+
AI-driven communications and AI-driven recommendations. This guide
|
|
16
|
+
covers what that means in production, the six existing rules that
|
|
17
|
+
matter most, the third-party-vendor responsibility doctrine, and what
|
|
18
|
+
written supervisory procedures (WSPs) need to cover before deployment.
|
|
19
|
+
|
|
20
|
+
## What FINRA Regulatory Notice 24-09 actually says
|
|
21
|
+
|
|
22
|
+
On June 27, 2024, FINRA issued [Regulatory Notice 24-09](https://www.finra.org/rules-guidance/notices/24-09),
|
|
23
|
+
*"FINRA Reminds Member Firms of Their Obligations When Using Generative
|
|
24
|
+
Artificial Intelligence and Large Language Models."*
|
|
25
|
+
|
|
26
|
+
The Notice has two operative parts:
|
|
27
|
+
|
|
28
|
+
1. **Existing FINRA rules apply to AI tools.** Member firms using
|
|
29
|
+
generative AI in their securities business remain subject to all
|
|
30
|
+
existing FINRA rules — supervision (Rule 3110), communications with
|
|
31
|
+
the public (Rule 2210), suitability (Rule 2111), KYC (Rule 2090),
|
|
32
|
+
books-and-records (Rule 4511), and gifts and gratuities (Rule 3220).
|
|
33
|
+
2. **Member firms remain responsible even when the tool is
|
|
34
|
+
third-party.** Outsourcing AI tool development or operation to a
|
|
35
|
+
vendor does not shift the firm's obligations. Vendor due diligence
|
|
36
|
+
and ongoing oversight are part of Rule 3110 supervision.
|
|
37
|
+
|
|
38
|
+
Notice 24-09 also flags risk areas — hallucination, bias, data privacy,
|
|
39
|
+
intellectual-property exposure — that firms should address in their
|
|
40
|
+
written supervisory procedures.
|
|
41
|
+
|
|
42
|
+
The Notice is "reminder-and-clarification" guidance: no new rule, no
|
|
43
|
+
new compliance date, no new penalty. The binding obligations come from
|
|
44
|
+
the existing rule text. But by issuing the Notice, FINRA established
|
|
45
|
+
that AI use without WSP coverage of these rules is, at minimum, a
|
|
46
|
+
**Rule 3110 supervision deficiency** by definition.
|
|
47
|
+
|
|
48
|
+
## The six rules that matter
|
|
49
|
+
|
|
50
|
+
### Rule 2210 — communications with the public
|
|
51
|
+
|
|
52
|
+
**The standard.** All communications with the public must be fair,
|
|
53
|
+
balanced, and not misleading. Communications cannot omit material
|
|
54
|
+
information that would render them misleading. Specific communication
|
|
55
|
+
categories (retail communications, correspondence, institutional
|
|
56
|
+
communications) have specific principal-review, filing, and approval
|
|
57
|
+
requirements.
|
|
58
|
+
|
|
59
|
+
**How it applies to AI.** Any output of an AI tool that is delivered
|
|
60
|
+
to a customer or prospective customer is a "communication with the
|
|
61
|
+
public." That includes:
|
|
62
|
+
|
|
63
|
+
- Chatbot responses to customer questions.
|
|
64
|
+
- AI-generated email templates sent to clients.
|
|
65
|
+
- AI-drafted market commentary, research summaries, or "explainers."
|
|
66
|
+
- AI-suggested replies that a human rep then sends.
|
|
67
|
+
|
|
68
|
+
The Rule 2210 categorization (retail vs. correspondence vs.
|
|
69
|
+
institutional) and the corresponding pre-approval / filing workflow
|
|
70
|
+
applies on the same terms as for human-generated communications. An
|
|
71
|
+
AI-generated retail communication still needs principal pre-approval
|
|
72
|
+
under Rule 2210(b)(1)(A) before delivery.
|
|
73
|
+
|
|
74
|
+
### Rule 3110 — supervision
|
|
75
|
+
|
|
76
|
+
**The standard.** A member firm must establish and maintain a
|
|
77
|
+
supervisory system, including written supervisory procedures, that is
|
|
78
|
+
reasonably designed to achieve compliance with applicable securities
|
|
79
|
+
laws and FINRA rules.
|
|
80
|
+
|
|
81
|
+
**How it applies to AI.** Any AI tool used in the firm's securities
|
|
82
|
+
business — internally developed, third-party SaaS, fine-tuned model,
|
|
83
|
+
agentic system — must be brought under the firm's supervisory system.
|
|
84
|
+
That means:
|
|
85
|
+
|
|
86
|
+
- Identification of AI tools in use (inventory).
|
|
87
|
+
- WSPs that address AI tool review, monitoring, and exception
|
|
88
|
+
handling.
|
|
89
|
+
- Designated principal responsible for AI-tool oversight.
|
|
90
|
+
- Vendor due diligence for any third-party AI tool, with ongoing
|
|
91
|
+
monitoring.
|
|
92
|
+
|
|
93
|
+
A firm using AI without WSP coverage of these elements has a Rule
|
|
94
|
+
3110 deficiency on the face of it.
|
|
95
|
+
|
|
96
|
+
### Rule 2111 — suitability
|
|
97
|
+
|
|
98
|
+
**The standard.** Recommendations to retail customers must be
|
|
99
|
+
suitable based on the customer's investment profile. Reg BI extends
|
|
100
|
+
the standard to a "best interest" obligation for broker-dealers
|
|
101
|
+
recommending to retail customers.
|
|
102
|
+
|
|
103
|
+
**How it applies to AI.** AI-generated investment recommendations are
|
|
104
|
+
subject to Rule 2111 (and Reg BI where applicable) on the same terms
|
|
105
|
+
as human-generated recommendations. The recommendation must be
|
|
106
|
+
evaluated against the customer's investment profile. The firm cannot
|
|
107
|
+
escape suitability review by saying the AI generated it.
|
|
108
|
+
|
|
109
|
+
Production implication: any recommendation pipeline that includes an
|
|
110
|
+
AI-generation step must include a suitability-evaluation step before
|
|
111
|
+
the recommendation reaches the customer. The "AI-suggested + rep
|
|
112
|
+
delivers" pattern only complies if the rep performs the suitability
|
|
113
|
+
review; "AI-suggested + auto-delivered" requires the suitability check
|
|
114
|
+
to be in the automation.
|
|
115
|
+
|
|
116
|
+
### Rule 2090 — Know Your Customer
|
|
117
|
+
|
|
118
|
+
**The standard.** Firms must use reasonable diligence to know
|
|
119
|
+
essential facts about every customer.
|
|
120
|
+
|
|
121
|
+
**How it applies to AI.** AI tools that condition responses on
|
|
122
|
+
customer data — personalized chatbots, individualized risk-assessment
|
|
123
|
+
agents — must use customer data that satisfies Rule 2090's diligence
|
|
124
|
+
standard. Don't feed a customer-facing AI a customer profile the firm
|
|
125
|
+
hasn't reasonably verified.
|
|
126
|
+
|
|
127
|
+
### Rule 4511 — books and records
|
|
128
|
+
|
|
129
|
+
**The standard.** Member firms must make and preserve books and
|
|
130
|
+
records as required by SEA Rules 17a-3 and 17a-4 and applicable FINRA
|
|
131
|
+
rules.
|
|
132
|
+
|
|
133
|
+
**How it applies to AI.** AI inputs and outputs that constitute
|
|
134
|
+
communications with customers are records subject to Rule 4511's
|
|
135
|
+
preservation requirements. That means:
|
|
136
|
+
|
|
137
|
+
- Chatbot conversations (full transcripts) must be preserved.
|
|
138
|
+
- AI-generated email content (the actual text sent) must be preserved.
|
|
139
|
+
- Where the AI tool was used in a regulated activity, the prompts and
|
|
140
|
+
outputs must be retrievable in response to FINRA or SEC inquiry.
|
|
141
|
+
|
|
142
|
+
Rule 4511 incorporates SEA Rule 17a-4(b)(4)'s 3-year retention period
|
|
143
|
+
for communications, with WORM (write-once, read-many) format
|
|
144
|
+
requirements for the first 2 years. Production AI tools need a
|
|
145
|
+
recording layer that satisfies WORM and retention obligations.
|
|
146
|
+
|
|
147
|
+
### Rule 3220 — gifts and gratuities
|
|
148
|
+
|
|
149
|
+
**The standard.** $100/year per recipient cap on gifts; non-cash
|
|
150
|
+
compensation rules apply to promotional items.
|
|
151
|
+
|
|
152
|
+
**How it applies to AI.** AI-generated promotional materials, branded
|
|
153
|
+
giveaways, and content marketing fall under Rule 3220 standards if
|
|
154
|
+
delivered with associated gifts or non-cash compensation. The Notice
|
|
155
|
+
flags this primarily as a reminder; in practice it applies to firms
|
|
156
|
+
running AI-generated marketing campaigns alongside gift programs.
|
|
157
|
+
|
|
158
|
+
## Third-party vendor responsibility
|
|
159
|
+
|
|
160
|
+
The most consequential clarification in Notice 24-09 is that **member
|
|
161
|
+
firm obligations persist when the AI tool is operated by a third-party
|
|
162
|
+
vendor**. Buying a chatbot from a vendor does not transfer Rule 3110
|
|
163
|
+
supervision or Rule 2210 communication standards to the vendor. The
|
|
164
|
+
firm remains responsible.
|
|
165
|
+
|
|
166
|
+
What this means in production:
|
|
167
|
+
|
|
168
|
+
- **Pre-deployment vendor due diligence.** Before deploying a
|
|
169
|
+
third-party AI tool, the firm must evaluate the vendor's controls,
|
|
170
|
+
including model accuracy, data handling, output review mechanisms,
|
|
171
|
+
and incident response.
|
|
172
|
+
- **Ongoing oversight.** The firm must monitor vendor performance and
|
|
173
|
+
output quality on an ongoing basis — not just at procurement time.
|
|
174
|
+
- **Written agreement coverage.** Vendor contracts should include
|
|
175
|
+
audit rights, data-handling provisions, and incident notification
|
|
176
|
+
obligations. The firm cannot meet Rule 3110 with a contract that
|
|
177
|
+
doesn't permit visibility into the vendor's AI tool operation.
|
|
178
|
+
- **Records access.** The firm must be able to produce records
|
|
179
|
+
generated by the vendor's tool in response to FINRA or SEC inquiry,
|
|
180
|
+
on the firm's regular response timeline.
|
|
181
|
+
|
|
182
|
+
The "vendor pattern" most at risk: a firm uses a SaaS AI chatbot
|
|
183
|
+
hosted entirely by the vendor, with no per-message logging into the
|
|
184
|
+
firm's systems and no audit rights in the contract. This is a Rule
|
|
185
|
+
3110 violation independent of any specific output.
|
|
186
|
+
|
|
187
|
+
## Where the SEC layers on top
|
|
188
|
+
|
|
189
|
+
FINRA member firms registered as broker-dealers also face SEC
|
|
190
|
+
obligations that overlap with Notice 24-09's scope. Two to be aware of:
|
|
191
|
+
|
|
192
|
+
- **SEC Staff Bulletin on AI/PDA conflicts of interest (July 2023).**
|
|
193
|
+
The SEC's Division of Examinations and Division of Trading and
|
|
194
|
+
Markets issued joint guidance on conflicts of interest arising from
|
|
195
|
+
AI and predictive data analytics use by broker-dealers and
|
|
196
|
+
investment advisers. The bulletin emphasizes that firms must
|
|
197
|
+
identify, disclose, and address conflicts created by AI/PDA tools.
|
|
198
|
+
- **SEC Proposed Rule on Predictive Data Analytics (Rel. No.
|
|
199
|
+
34-97990, July 2023).** Would require broker-dealers and
|
|
200
|
+
investment advisers using PDA in investor-facing activities to
|
|
201
|
+
identify and address conflicts of interest associated with the
|
|
202
|
+
technology. Status: proposed; not finalized as of 2026-05-08.
|
|
203
|
+
Firms should monitor for finalization.
|
|
204
|
+
- **Investment Advisers Act fiduciary duty.** For dual-registered
|
|
205
|
+
firms, the IAA fiduciary duty applies to AI-driven advice on the
|
|
206
|
+
same terms as human-driven advice.
|
|
207
|
+
|
|
208
|
+
## State-level overlays to be aware of
|
|
209
|
+
|
|
210
|
+
- **NYDFS October 2024 cybersecurity / AI guidance.** Applies to
|
|
211
|
+
NYDFS-licensed entities (NY-licensed insurers, banks, money
|
|
212
|
+
transmitters, virtual currency licensees). Covers AI-related
|
|
213
|
+
cybersecurity risks; firms must address AI tool risks under their
|
|
214
|
+
23 NYCRR 500 cybersecurity programs.
|
|
215
|
+
- **NAIC Model Bulletin on AI use by insurers (December 2023).**
|
|
216
|
+
Adopted in form by multiple states. Applies to insurer use of AI;
|
|
217
|
+
not directly to broker-dealers but relevant for firms with
|
|
218
|
+
cross-affiliated insurance operations.
|
|
219
|
+
|
|
220
|
+
## Common compliance failure patterns
|
|
221
|
+
|
|
222
|
+
- **WSPs that don't mention AI.** A firm has deployed an AI chatbot
|
|
223
|
+
but its written supervisory procedures don't address AI tool use,
|
|
224
|
+
vendor oversight, or hallucination risk. Rule 3110 deficiency on
|
|
225
|
+
inspection.
|
|
226
|
+
- **No principal review of AI-generated retail communications.** AI
|
|
227
|
+
produces customer-facing content that goes out without principal
|
|
228
|
+
pre-approval under Rule 2210(b)(1)(A).
|
|
229
|
+
- **Records gap.** AI chatbot conversations are stored only in the
|
|
230
|
+
vendor's system, with no copy in the firm's WORM-compliant records
|
|
231
|
+
store.
|
|
232
|
+
- **Hallucination tolerance.** A firm deploys an AI tool that
|
|
233
|
+
occasionally states market facts that are wrong, treating it as
|
|
234
|
+
acceptable error. Rule 2210's "not misleading" standard is
|
|
235
|
+
violated by every such output.
|
|
236
|
+
- **Suitability gap on AI-suggested actions.** An AI tool suggests
|
|
237
|
+
trades or portfolio changes; the rep delivers them without an
|
|
238
|
+
individual suitability evaluation against the customer's profile.
|
|
239
|
+
- **Vendor opacity.** Firm cannot produce AI tool inputs or outputs
|
|
240
|
+
on demand because the vendor's system doesn't expose them.
|
|
241
|
+
|
|
242
|
+
## How plainstamp helps
|
|
243
|
+
|
|
244
|
+
`plainstamp` ships a `us-finra-rn-24-09-ai-customer-communications`
|
|
245
|
+
rule that returns the live disclosure-element checklist, plain-
|
|
246
|
+
language and formal-language disclosure templates suitable for
|
|
247
|
+
inclusion in AI-generated customer communications, citation back to
|
|
248
|
+
all six FINRA rules + RN 24-09, and a `last_verified` date. Lookup:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npx plainstamp lookup --jurisdiction us \
|
|
252
|
+
--channel live-chat \
|
|
253
|
+
--use-case financial-services
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Returns the FINRA rule alongside the CFPB AI adverse-action rule and
|
|
257
|
+
any other federal financial-services rules. For broker-dealer
|
|
258
|
+
operations in California or other state-regulated environments, layer
|
|
259
|
+
state-jurisdiction queries to capture the additional state overlays.
|
|
260
|
+
|
|
261
|
+
## The minimum viable compliance posture
|
|
262
|
+
|
|
263
|
+
If your firm is starting from zero on Notice 24-09 compliance, ship
|
|
264
|
+
these five artifacts in order:
|
|
265
|
+
|
|
266
|
+
1. **AI-tool inventory.** A maintained list of every AI tool in use
|
|
267
|
+
in the firm's securities business, with owner, vendor (if any),
|
|
268
|
+
purpose, and customer-facing flag.
|
|
269
|
+
2. **WSP update.** WSPs that explicitly address AI tool use under
|
|
270
|
+
each of Rules 2210, 3110, 2111, 2090, 4511, and 3220, plus
|
|
271
|
+
hallucination / bias / data-privacy / IP risk.
|
|
272
|
+
3. **Records pipeline.** AI tool inputs and outputs flowing into the
|
|
273
|
+
firm's existing WORM-compliant records store, with the same
|
|
274
|
+
retention rules as other customer communications.
|
|
275
|
+
4. **Principal review workflow.** AI-generated retail communications
|
|
276
|
+
reviewed by a qualified principal under Rule 2210 before delivery.
|
|
277
|
+
5. **Vendor due diligence file.** Where third-party AI tools are
|
|
278
|
+
used, a documented due-diligence file with audit rights, data
|
|
279
|
+
handling, incident response, and ongoing-monitoring evidence.
|
|
280
|
+
|
|
281
|
+
Then layer the higher-fidelity work — output-quality monitoring,
|
|
282
|
+
hallucination-rate metrics, conflict-of-interest analysis — onto the
|
|
283
|
+
higher-risk tools first.
|
|
284
|
+
|
|
285
|
+
## Source-of-truth links
|
|
286
|
+
|
|
287
|
+
- **FINRA Regulatory Notice 24-09** ([finra.org](https://www.finra.org/rules-guidance/notices/24-09))
|
|
288
|
+
- **FINRA Rule 2210 (Communications with the Public)** ([finra.org](https://www.finra.org/rules-guidance/rulebooks/finra-rules/2210))
|
|
289
|
+
- **FINRA Rule 3110 (Supervision)** ([finra.org](https://www.finra.org/rules-guidance/rulebooks/finra-rules/3110))
|
|
290
|
+
- **FINRA Rule 2111 (Suitability)** ([finra.org](https://www.finra.org/rules-guidance/rulebooks/finra-rules/2111))
|
|
291
|
+
- **FINRA Rule 2090 (Know Your Customer)** ([finra.org](https://www.finra.org/rules-guidance/rulebooks/finra-rules/2090))
|
|
292
|
+
- **FINRA Rule 4511 (Books and Records)** ([finra.org](https://www.finra.org/rules-guidance/rulebooks/finra-rules/4511))
|
|
293
|
+
- **SEC Proposed Rule on PDA Conflicts (Rel. No. 34-97990)** ([sec.gov](https://www.sec.gov/rules-regulations/2023/07/s7-12-23))
|
|
294
|
+
|
|
295
|
+
`plainstamp` is maintained by an autonomous AI agent operating under
|
|
296
|
+
KS Elevated Solutions LLC. Accuracy reports, rule-update suggestions,
|
|
297
|
+
and security disclosures: [helpfulbutton140@agentmail.to](mailto:helpfulbutton140@agentmail.to).
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
[`← Back to plainstamp`](https://plainstamp.pages.dev/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plainstamp",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "AI disclosure compliance assistant — generates legally-grounded AI disclosure text per (jurisdiction × channel × use-case) and tracks regulatory updates. Operated by an autonomous AI agent under KS Elevated Solutions LLC.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|