@rotorsoft/act 1.9.0 → 1.10.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.
Files changed (39) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/@types/act.d.ts +34 -2
  3. package/dist/@types/act.d.ts.map +1 -1
  4. package/dist/@types/builders/act-builder.d.ts.map +1 -1
  5. package/dist/@types/builders/state-builder.d.ts +31 -1
  6. package/dist/@types/builders/state-builder.d.ts.map +1 -1
  7. package/dist/@types/internal/event-sourcing.d.ts +7 -2
  8. package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
  9. package/dist/@types/internal/index.d.ts +1 -0
  10. package/dist/@types/internal/index.d.ts.map +1 -1
  11. package/dist/@types/internal/reactions.d.ts +5 -4
  12. package/dist/@types/internal/reactions.d.ts.map +1 -1
  13. package/dist/@types/internal/sensitive.d.ts +147 -0
  14. package/dist/@types/internal/sensitive.d.ts.map +1 -0
  15. package/dist/@types/internal/tracing.d.ts.map +1 -1
  16. package/dist/@types/types/action.d.ts +57 -0
  17. package/dist/@types/types/action.d.ts.map +1 -1
  18. package/dist/@types/types/registry.d.ts +9 -1
  19. package/dist/@types/types/registry.d.ts.map +1 -1
  20. package/dist/@types/types/schemas.d.ts +36 -0
  21. package/dist/@types/types/schemas.d.ts.map +1 -1
  22. package/dist/{chunk-F4S2JOPN.js → chunk-3ZTFNAY7.js} +2 -2
  23. package/dist/chunk-XSBT63QX.js +267 -0
  24. package/dist/chunk-XSBT63QX.js.map +1 -0
  25. package/dist/index.cjs +291 -78
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +155 -32
  28. package/dist/index.js.map +1 -1
  29. package/dist/test/index.cjs +62 -56
  30. package/dist/test/index.cjs.map +1 -1
  31. package/dist/test/index.js +11 -11
  32. package/dist/test/index.js.map +1 -1
  33. package/dist/types/index.cjs +52 -34
  34. package/dist/types/index.cjs.map +1 -1
  35. package/dist/types/index.js +9 -3
  36. package/package.json +1 -1
  37. package/dist/chunk-PMAZTOSO.js +0 -164
  38. package/dist/chunk-PMAZTOSO.js.map +0 -1
  39. /package/dist/{chunk-F4S2JOPN.js.map → chunk-3ZTFNAY7.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -51,6 +51,8 @@ __export(index_exports, {
51
51
  NonRetryableError: () => NonRetryableError,
52
52
  PackageSchema: () => PackageSchema,
53
53
  QuerySchema: () => QuerySchema,
54
+ REDACTED: () => REDACTED,
55
+ SHREDDED: () => SHREDDED,
54
56
  SNAP_EVENT: () => SNAP_EVENT,
55
57
  StreamClosedError: () => StreamClosedError,
56
58
  TOMBSTONE_EVENT: () => TOMBSTONE_EVENT,
@@ -67,6 +69,7 @@ __export(index_exports, {
67
69
  port: () => port,
68
70
  projection: () => projection,
69
71
  scoped: () => scoped,
72
+ sensitive: () => sensitive,
70
73
  sleep: () => sleep,
71
74
  slice: () => slice,
72
75
  state: () => state,
@@ -380,55 +383,150 @@ var NonRetryableError = class extends Error {
380
383
  };
381
384
 
382
385
  // src/utils.ts
383
- var import_zod3 = require("zod");
386
+ var import_zod4 = require("zod");
384
387
 
385
388
  // src/config.ts
386
389
  var fs = __toESM(require("fs"), 1);
387
- var import_zod2 = require("zod");
390
+ var import_zod3 = require("zod");
388
391
 
389
392
  // src/types/schemas.ts
393
+ var import_zod2 = require("zod");
394
+
395
+ // src/internal/sensitive.ts
390
396
  var import_zod = require("zod");
391
- var ZodEmpty = import_zod.z.record(import_zod.z.string(), import_zod.z.never());
392
- var ActorSchema = import_zod.z.object({
393
- id: import_zod.z.string(),
394
- name: import_zod.z.string()
397
+ var REDACTED = "[REDACTED]";
398
+ var SHREDDED = "[SHREDDED]";
399
+ var _registry = import_zod.z.registry();
400
+ function is_pii(schema) {
401
+ let cur = schema;
402
+ while (true) {
403
+ if (_registry.has(cur)) return true;
404
+ const inner = cur._def?.innerType;
405
+ if (!inner || inner === cur) return false;
406
+ cur = inner;
407
+ }
408
+ }
409
+ function pii_fields(schema) {
410
+ const shape = schema.shape;
411
+ if (!shape || typeof shape !== "object") return [];
412
+ const fields = [];
413
+ for (const key of Object.keys(shape)) {
414
+ if (is_pii(shape[key])) fields.push(key);
415
+ }
416
+ return fields;
417
+ }
418
+ function pii_split(emitted, fields) {
419
+ const rec = emitted.data;
420
+ const clean = {};
421
+ const pii = {};
422
+ for (const k of Object.keys(rec)) {
423
+ if (fields.includes(k)) pii[k] = rec[k];
424
+ else clean[k] = rec[k];
425
+ }
426
+ return { name: emitted.name, data: clean, pii };
427
+ }
428
+ function pii_merge(event, fields) {
429
+ const data = event.data;
430
+ const pii = event.pii;
431
+ if (pii != null) {
432
+ return {
433
+ ...event,
434
+ data: { ...data, ...pii }
435
+ };
436
+ }
437
+ const shredded = { ...data };
438
+ for (const f of fields) shredded[f] = SHREDDED;
439
+ return {
440
+ ...event,
441
+ data: shredded
442
+ };
443
+ }
444
+ function pii_gate(event, fields, predicate, actor) {
445
+ const data = event.data;
446
+ if (event.pii == null) {
447
+ const shredded = { ...data };
448
+ for (const f of fields) shredded[f] = SHREDDED;
449
+ return {
450
+ ...event,
451
+ data: shredded
452
+ };
453
+ }
454
+ const allowed = !!actor && !!predicate && predicate(event, actor);
455
+ if (allowed) {
456
+ return {
457
+ ...event,
458
+ data: {
459
+ ...data,
460
+ ...event.pii
461
+ }
462
+ };
463
+ }
464
+ const redacted = { ...data };
465
+ for (const f of fields) redacted[f] = REDACTED;
466
+ return {
467
+ ...event,
468
+ data: redacted
469
+ };
470
+ }
471
+ function pii_strip(event, fields) {
472
+ const data = event.data;
473
+ const stripped = {};
474
+ for (const k of Object.keys(data)) {
475
+ if (!fields.includes(k)) stripped[k] = data[k];
476
+ }
477
+ const { pii: _drop_pii, ...rest } = event;
478
+ return {
479
+ ...rest,
480
+ data: stripped
481
+ };
482
+ }
483
+
484
+ // src/types/schemas.ts
485
+ var ZodEmpty = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.never());
486
+ function sensitive(schema) {
487
+ _registry.add(schema, { sensitive: true });
488
+ return schema;
489
+ }
490
+ var ActorSchema = import_zod2.z.object({
491
+ id: import_zod2.z.string(),
492
+ name: import_zod2.z.string()
395
493
  }).loose().readonly();
396
- var TargetSchema = import_zod.z.object({
397
- stream: import_zod.z.string(),
494
+ var TargetSchema = import_zod2.z.object({
495
+ stream: import_zod2.z.string(),
398
496
  actor: ActorSchema,
399
- expectedVersion: import_zod.z.number().optional()
497
+ expectedVersion: import_zod2.z.number().optional()
400
498
  }).loose().readonly();
401
- var CausationEventSchema = import_zod.z.object({
402
- id: import_zod.z.number(),
403
- name: import_zod.z.string(),
404
- stream: import_zod.z.string()
499
+ var CausationEventSchema = import_zod2.z.object({
500
+ id: import_zod2.z.number(),
501
+ name: import_zod2.z.string(),
502
+ stream: import_zod2.z.string()
405
503
  });
406
- var EventMetaSchema = import_zod.z.object({
407
- correlation: import_zod.z.string(),
408
- causation: import_zod.z.object({
409
- action: TargetSchema.and(import_zod.z.object({ name: import_zod.z.string() })).optional(),
504
+ var EventMetaSchema = import_zod2.z.object({
505
+ correlation: import_zod2.z.string(),
506
+ causation: import_zod2.z.object({
507
+ action: TargetSchema.and(import_zod2.z.object({ name: import_zod2.z.string() })).optional(),
410
508
  event: CausationEventSchema.optional()
411
509
  })
412
510
  }).readonly();
413
- var CommittedMetaSchema = import_zod.z.object({
414
- id: import_zod.z.number(),
415
- stream: import_zod.z.string(),
416
- version: import_zod.z.number(),
417
- created: import_zod.z.date(),
511
+ var CommittedMetaSchema = import_zod2.z.object({
512
+ id: import_zod2.z.number(),
513
+ stream: import_zod2.z.string(),
514
+ version: import_zod2.z.number(),
515
+ created: import_zod2.z.date(),
418
516
  meta: EventMetaSchema
419
517
  }).readonly();
420
- var QuerySchema = import_zod.z.object({
421
- stream: import_zod.z.string().optional(),
422
- names: import_zod.z.string().array().optional(),
423
- before: import_zod.z.number().optional(),
424
- after: import_zod.z.number().optional(),
425
- limit: import_zod.z.number().optional(),
426
- created_before: import_zod.z.date().optional(),
427
- created_after: import_zod.z.date().optional(),
428
- backward: import_zod.z.boolean().optional(),
429
- correlation: import_zod.z.string().optional(),
430
- with_snaps: import_zod.z.boolean().optional(),
431
- stream_exact: import_zod.z.boolean().optional()
518
+ var QuerySchema = import_zod2.z.object({
519
+ stream: import_zod2.z.string().optional(),
520
+ names: import_zod2.z.string().array().optional(),
521
+ before: import_zod2.z.number().optional(),
522
+ after: import_zod2.z.number().optional(),
523
+ limit: import_zod2.z.number().optional(),
524
+ created_before: import_zod2.z.date().optional(),
525
+ created_after: import_zod2.z.date().optional(),
526
+ backward: import_zod2.z.boolean().optional(),
527
+ correlation: import_zod2.z.string().optional(),
528
+ with_snaps: import_zod2.z.boolean().optional(),
529
+ stream_exact: import_zod2.z.boolean().optional()
432
530
  }).readonly();
433
531
 
434
532
  // src/types/index.ts
@@ -448,13 +546,13 @@ var LogLevels = [
448
546
  ];
449
547
 
450
548
  // src/config.ts
451
- var PackageSchema = import_zod2.z.object({
452
- name: import_zod2.z.string().min(1),
453
- version: import_zod2.z.string().min(1),
454
- description: import_zod2.z.string().min(1).optional(),
455
- author: import_zod2.z.object({ name: import_zod2.z.string().min(1), email: import_zod2.z.string().optional() }).optional().or(import_zod2.z.string().min(1)).optional(),
456
- license: import_zod2.z.string().min(1).optional(),
457
- dependencies: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional()
549
+ var PackageSchema = import_zod3.z.object({
550
+ name: import_zod3.z.string().min(1),
551
+ version: import_zod3.z.string().min(1),
552
+ description: import_zod3.z.string().min(1).optional(),
553
+ author: import_zod3.z.object({ name: import_zod3.z.string().min(1), email: import_zod3.z.string().optional() }).optional().or(import_zod3.z.string().min(1)).optional(),
554
+ license: import_zod3.z.string().min(1).optional(),
555
+ dependencies: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string()).optional()
458
556
  });
459
557
  var FALLBACK_PACKAGE = {
460
558
  name: "act-fallback",
@@ -472,10 +570,10 @@ var getPackage = () => {
472
570
  };
473
571
  var pkgLoadError;
474
572
  var BaseSchema = PackageSchema.extend({
475
- env: import_zod2.z.enum(Environments),
476
- logLevel: import_zod2.z.enum(LogLevels),
477
- logSingleLine: import_zod2.z.boolean(),
478
- sleepMs: import_zod2.z.number().int().min(0).max(5e3)
573
+ env: import_zod3.z.enum(Environments),
574
+ logLevel: import_zod3.z.enum(LogLevels),
575
+ logSingleLine: import_zod3.z.boolean(),
576
+ sleepMs: import_zod3.z.number().int().min(0).max(5e3)
479
577
  });
480
578
  var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
481
579
  var env = NODE_ENV || "development";
@@ -506,8 +604,8 @@ var validate = (target, payload, schema) => {
506
604
  try {
507
605
  return schema ? schema.parse(payload) : payload;
508
606
  } catch (error) {
509
- if (error instanceof import_zod3.ZodError) {
510
- throw new ValidationError(target, payload, (0, import_zod3.prettifyError)(error));
607
+ if (error instanceof import_zod4.ZodError) {
608
+ throw new ValidationError(target, payload, (0, import_zod4.prettifyError)(error));
511
609
  }
512
610
  throw new ValidationError(target, payload, error);
513
611
  }
@@ -2334,7 +2432,7 @@ async function scan(source, opts = {}, callback) {
2334
2432
  }
2335
2433
  };
2336
2434
  }
2337
- async function load(me, stream, callback, asOf) {
2435
+ async function load(me, stream, callback, asOf, actor) {
2338
2436
  const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
2339
2437
  const cached = timeTravel ? void 0 : await cache2().get(stream);
2340
2438
  const cache_hit = !!cached;
@@ -2346,15 +2444,16 @@ async function load(me, stream, callback, asOf) {
2346
2444
  let event;
2347
2445
  await store2().query(
2348
2446
  (e) => {
2349
- event = e;
2350
2447
  version = e.version;
2448
+ const typed = e;
2449
+ event = me.view(typed, actor);
2351
2450
  if (e.name === SNAP_EVENT) {
2352
2451
  state2 = e.data;
2353
2452
  snaps++;
2354
2453
  patches = 0;
2355
2454
  replayed++;
2356
2455
  } else if (me.patch[e.name]) {
2357
- state2 = (0, import_act_patch.patch)(state2, me.patch[e.name](event, state2));
2456
+ state2 = (0, import_act_patch.patch)(state2, me.patch[e.name](typed, state2));
2358
2457
  patches++;
2359
2458
  replayed++;
2360
2459
  } else if (e.name !== TOMBSTONE_EVENT) {
@@ -2397,7 +2496,13 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
2397
2496
  const maxRetries = opts?.maxRetries ?? 0;
2398
2497
  for (let attempt = 0; ; attempt++) {
2399
2498
  try {
2400
- const snapshot = await load(me, stream);
2499
+ const snapshot = await load(
2500
+ me,
2501
+ stream,
2502
+ void 0,
2503
+ void 0,
2504
+ target.actor
2505
+ );
2401
2506
  if (snapshot.event?.name === TOMBSTONE_EVENT)
2402
2507
  throw new StreamClosedError(stream);
2403
2508
  const expected = expectedVersion ?? snapshot.event?.version;
@@ -2434,10 +2539,10 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
2434
2539
  }
2435
2540
  }
2436
2541
  }
2437
- const emitted = tuples.map(([name, data]) => ({
2438
- name,
2439
- data: skipValidation ? data : validate(name, data, me.events[name])
2440
- }));
2542
+ const emitted = tuples.map(([name, data]) => {
2543
+ const validated2 = skipValidation ? data : validate(name, data, me.events[name]);
2544
+ return me.message({ name, data: validated2 });
2545
+ });
2441
2546
  const meta = {
2442
2547
  correlation: reactingTo?.meta.correlation || correlator({
2443
2548
  action: action2,
@@ -2449,8 +2554,8 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
2449
2554
  action: {
2450
2555
  name: action2,
2451
2556
  ...target
2452
- // payload intentionally omitted: it can be large or contain PII,
2453
- // and callers correlate via the correlation id when they need it.
2557
+ // payload intentionally omitted from causation metadata
2558
+ // callers correlate via the correlation id when they need it.
2454
2559
  },
2455
2560
  event: reactingTo ? {
2456
2561
  id: reactingTo.id,
@@ -2483,7 +2588,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
2483
2588
  state2 = (0, import_act_patch.patch)(state2, p);
2484
2589
  patches++;
2485
2590
  return {
2486
- event,
2591
+ event: me.view(event, target.actor),
2487
2592
  state: state2,
2488
2593
  version: event.version,
2489
2594
  patches,
@@ -2568,7 +2673,7 @@ var traced = (inner, exit, entry) => (async (...args) => {
2568
2673
  return result;
2569
2674
  });
2570
2675
  function buildEs(logger, correlator = defaultCorrelator) {
2571
- const boundAction = (me, actionName, target, payload, reactingTo, skipValidation = false) => action(
2676
+ const bound_action = (me, actionName, target, payload, reactingTo, skipValidation = false) => action(
2572
2677
  me,
2573
2678
  actionName,
2574
2679
  target,
@@ -2581,7 +2686,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
2581
2686
  return {
2582
2687
  snap,
2583
2688
  load,
2584
- action: boundAction,
2689
+ action: bound_action,
2585
2690
  tombstone
2586
2691
  };
2587
2692
  }
@@ -2611,7 +2716,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
2611
2716
  );
2612
2717
  }),
2613
2718
  action: traced(
2614
- boundAction,
2719
+ bound_action,
2615
2720
  (snapshots, _me, _action, target) => {
2616
2721
  const committed = snapshots.filter((s) => s.event);
2617
2722
  if (committed.length) {
@@ -2904,7 +3009,7 @@ var DrainController = class {
2904
3009
  };
2905
3010
 
2906
3011
  // src/internal/merge.ts
2907
- var import_zod4 = require("zod");
3012
+ var import_zod5 = require("zod");
2908
3013
  function baseTypeName(zodType) {
2909
3014
  let t = zodType;
2910
3015
  while (typeof t.unwrap === "function") {
@@ -2913,7 +3018,7 @@ function baseTypeName(zodType) {
2913
3018
  return t.constructor.name;
2914
3019
  }
2915
3020
  function mergeSchemas(existing, incoming, stateName) {
2916
- if (existing instanceof import_zod4.ZodObject && incoming instanceof import_zod4.ZodObject) {
3021
+ if (existing instanceof import_zod5.ZodObject && incoming instanceof import_zod5.ZodObject) {
2917
3022
  const existingShape = existing.shape;
2918
3023
  const incomingShape = incoming.shape;
2919
3024
  for (const key of Object.keys(incomingShape)) {
@@ -3068,7 +3173,14 @@ function finalize(lease, handled, at, error, options, logger, failed_at) {
3068
3173
  };
3069
3174
  }
3070
3175
  function buildHandle(deps) {
3071
- const { logger, boundDo, boundLoad, boundQuery, boundQueryArray } = deps;
3176
+ const {
3177
+ logger,
3178
+ bound_do,
3179
+ bound_load,
3180
+ bound_query,
3181
+ bound_query_array,
3182
+ bound_forget
3183
+ } = deps;
3072
3184
  return async (lease, payloads) => {
3073
3185
  if (payloads.length === 0) return { lease, handled: 0, acked_at: lease.at };
3074
3186
  const stream = lease.stream;
@@ -3077,14 +3189,15 @@ function buildHandle(deps) {
3077
3189
  if (lease.retry > 0)
3078
3190
  logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
3079
3191
  const scopedApp = {
3080
- do: boundDo,
3081
- load: boundLoad,
3082
- query: boundQuery,
3083
- query_array: boundQueryArray
3192
+ do: bound_do,
3193
+ load: bound_load,
3194
+ query: bound_query,
3195
+ query_array: bound_query_array,
3196
+ forget: bound_forget
3084
3197
  };
3085
3198
  for (const payload of payloads) {
3086
3199
  const { event, handler } = payload;
3087
- scopedApp.do = (action2, target, actionPayload, reactingTo, skipValidation) => boundDo(
3200
+ scopedApp.do = (action2, target, actionPayload, reactingTo, skipValidation) => bound_do(
3088
3201
  action2,
3089
3202
  target,
3090
3203
  actionPayload,
@@ -3113,7 +3226,9 @@ function buildHandle(deps) {
3113
3226
  function buildHandleBatch(logger) {
3114
3227
  return async (lease, payloads, batchHandler) => {
3115
3228
  const stream = lease.stream;
3116
- const events = payloads.map((p) => p.event);
3229
+ const events = payloads.map(
3230
+ (p) => p.event
3231
+ );
3117
3232
  const options = payloads[0].options;
3118
3233
  if (lease.retry > 0)
3119
3234
  logger.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
@@ -3296,6 +3411,7 @@ var Act = class {
3296
3411
  _bound_load = this.load.bind(this);
3297
3412
  _bound_query = this.query.bind(this);
3298
3413
  _bound_query_array = this.query_array.bind(this);
3414
+ _bound_forget = this.forget.bind(this);
3299
3415
  /** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
3300
3416
  _handle;
3301
3417
  _handle_batch;
@@ -3341,10 +3457,11 @@ var Act = class {
3341
3457
  this._cd = buildDrain(this._logger);
3342
3458
  this._handle = buildHandle({
3343
3459
  logger: this._logger,
3344
- boundDo: this._bound_do,
3345
- boundLoad: this._bound_load,
3346
- boundQuery: this._bound_query,
3347
- boundQueryArray: this._bound_query_array
3460
+ bound_do: this._bound_do,
3461
+ bound_load: this._bound_load,
3462
+ bound_query: this._bound_query,
3463
+ bound_query_array: this._bound_query_array,
3464
+ bound_forget: this._bound_forget
3348
3465
  });
3349
3466
  this._handle_batch = buildHandleBatch(this._logger);
3350
3467
  const {
@@ -3576,7 +3693,7 @@ var Act = class {
3576
3693
  return snapshots;
3577
3694
  });
3578
3695
  }
3579
- async load(stateOrName, stream, callback, asOf) {
3696
+ async load(stateOrName, stream, callback, asOf, actor) {
3580
3697
  return this._scoped(async () => {
3581
3698
  let merged;
3582
3699
  if (typeof stateOrName === "string") {
@@ -3586,7 +3703,7 @@ var Act = class {
3586
3703
  } else {
3587
3704
  merged = this._states.get(stateOrName.name) || stateOrName;
3588
3705
  }
3589
- return await this._es.load(merged, stream, callback, asOf);
3706
+ return await this._es.load(merged, stream, callback, asOf, actor);
3590
3707
  });
3591
3708
  }
3592
3709
  /**
@@ -3680,6 +3797,34 @@ var Act = class {
3680
3797
  return events;
3681
3798
  });
3682
3799
  }
3800
+ /**
3801
+ * Wipe the sensitive-data payload for every event on the stream — see
3802
+ * {@link IAct.forget}. Application-level half of #566.
3803
+ *
3804
+ * Throws on adapters without `Store.forget_pii`, invalidates the cache
3805
+ * entry for the stream, emits the `forgotten` lifecycle event with the
3806
+ * row count. Idempotent: a second call returns `{eventCount: 0}` and
3807
+ * does NOT re-emit.
3808
+ *
3809
+ * @param stream - Target stream.
3810
+ * @returns `{eventCount}` — number of events whose PII column was wiped.
3811
+ */
3812
+ async forget(stream) {
3813
+ return this._scoped(async () => {
3814
+ const s = store2();
3815
+ if (!s.forget_pii) {
3816
+ throw new Error(
3817
+ `Store does not implement forget_pii \u2014 adapter cannot comply with sensitive-data erasure. Use an adapter that declares pii_isolation: true (e.g. @rotorsoft/act on the in-memory store).`
3818
+ );
3819
+ }
3820
+ const eventCount = await s.forget_pii(stream);
3821
+ await cache2().invalidate(stream);
3822
+ if (eventCount > 0) {
3823
+ this.emit("forgotten", { stream, at: /* @__PURE__ */ new Date(), eventCount });
3824
+ }
3825
+ return { eventCount };
3826
+ });
3827
+ }
3683
3828
  /**
3684
3829
  * Processes pending reactions by draining uncommitted events from the event store.
3685
3830
  *
@@ -4280,9 +4425,13 @@ function validateLaneReferences(registry, lanes) {
4280
4425
  }
4281
4426
  function act() {
4282
4427
  const states = /* @__PURE__ */ new Map();
4428
+ const _sf = /* @__PURE__ */ new Map();
4429
+ const _dp = /* @__PURE__ */ new Map();
4283
4430
  const registry = {
4284
4431
  actions: {},
4285
- events: {}
4432
+ events: {},
4433
+ sensitive_fields: (eventName) => _sf.get(eventName) ?? [],
4434
+ disclosure_predicate: (stateName) => _dp.get(stateName) ?? null
4286
4435
  };
4287
4436
  const pendingProjections = [];
4288
4437
  const batchHandlers = /* @__PURE__ */ new Map();
@@ -4393,6 +4542,58 @@ function act() {
4393
4542
  }
4394
4543
  finalizeDeprecations();
4395
4544
  validateLaneReferences(registry, lanes);
4545
+ for (const [eventName, reg] of Object.entries(
4546
+ registry.events
4547
+ )) {
4548
+ const fields = pii_fields(reg.schema);
4549
+ if (fields.length === 0) continue;
4550
+ _sf.set(eventName, fields);
4551
+ for (const [name, reaction] of reg.reactions) {
4552
+ const inner = reaction.handler;
4553
+ const wrapped = (event, stream, app) => inner(pii_strip(event, fields), stream, app);
4554
+ Object.defineProperty(wrapped, "name", { value: inner.name });
4555
+ reaction.handler = wrapped;
4556
+ reg.reactions.set(name, reaction);
4557
+ }
4558
+ }
4559
+ for (const state2 of states.values()) {
4560
+ if (state2.disclose) _dp.set(state2.name, state2.disclose);
4561
+ const fields_by_event = /* @__PURE__ */ new Map();
4562
+ for (const eventName of Object.keys(state2.events)) {
4563
+ const fields = _sf.get(eventName);
4564
+ if (fields) fields_by_event.set(eventName, fields);
4565
+ }
4566
+ if (fields_by_event.size === 0) continue;
4567
+ if (state2.snap) {
4568
+ const offending = [...fields_by_event.keys()];
4569
+ throw new Error(
4570
+ `State "${state2.name}" cannot snapshot \u2014 events {${offending.join(", ")}} carry sensitive fields. Snapshots write derived state into __snapshot__.data, which forget_pii cannot reach. Remove .snap() or remove sensitive(...) markers.`
4571
+ );
4572
+ }
4573
+ const disclose = state2.disclose ?? null;
4574
+ state2.view = (event, actor) => {
4575
+ const fields = fields_by_event.get(event.name);
4576
+ return fields ? pii_gate(event, fields, disclose, actor) : event;
4577
+ };
4578
+ state2.message = (validated) => {
4579
+ const fields = fields_by_event.get(validated.name);
4580
+ return fields ? pii_split(validated, fields) : validated;
4581
+ };
4582
+ for (const [eventName, fields] of fields_by_event) {
4583
+ const original = state2.patch[eventName];
4584
+ state2.patch[eventName] = (event, s) => original(pii_merge(event, fields), s);
4585
+ }
4586
+ }
4587
+ for (const [target, original] of batchHandlers) {
4588
+ const wrapped = async (events, stream) => {
4589
+ const stripped = events.map((e) => {
4590
+ const f = _sf.get(e.name);
4591
+ return f ? pii_strip(e, f) : e;
4592
+ });
4593
+ return original(stripped, stream);
4594
+ };
4595
+ batchHandlers.set(target, wrapped);
4596
+ }
4396
4597
  _built = true;
4397
4598
  }
4398
4599
  return new Act(
@@ -4560,7 +4761,12 @@ function state(entry) {
4560
4761
  name,
4561
4762
  init,
4562
4763
  patch: defaultPatch,
4563
- on: {}
4764
+ on: {},
4765
+ // Step delegates initialized as identity. `act().build()`
4766
+ // overrides on states with `sensitive(...)` events to bake in
4767
+ // the gate / split.
4768
+ view: (event) => event,
4769
+ message: (validated) => validated
4564
4770
  };
4565
4771
  const builder = action_builder(internal);
4566
4772
  return Object.assign(builder, {
@@ -4612,6 +4818,10 @@ function action_builder(state2) {
4612
4818
  internal.snap = snap2;
4613
4819
  return builder;
4614
4820
  },
4821
+ discloses(disclose) {
4822
+ internal.disclose = disclose;
4823
+ return builder;
4824
+ },
4615
4825
  build() {
4616
4826
  return internal;
4617
4827
  }
@@ -4802,6 +5012,8 @@ function writeLine(writer, line) {
4802
5012
  NonRetryableError,
4803
5013
  PackageSchema,
4804
5014
  QuerySchema,
5015
+ REDACTED,
5016
+ SHREDDED,
4805
5017
  SNAP_EVENT,
4806
5018
  StreamClosedError,
4807
5019
  TOMBSTONE_EVENT,
@@ -4818,6 +5030,7 @@ function writeLine(writer, line) {
4818
5030
  port,
4819
5031
  projection,
4820
5032
  scoped,
5033
+ sensitive,
4821
5034
  sleep,
4822
5035
  slice,
4823
5036
  state,