@hasna/events 0.1.14 → 0.1.16

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.
@@ -0,0 +1,130 @@
1
+ import type { EventData, EventEnvelope, EventInput, EventSeverity } from "./types.js";
2
+ /** Stable wire-profile identifier for cross-app events. */
3
+ export declare const APP_EVENT_V1_SCHEMA_VERSION: "hasna.app_event.v1";
4
+ export declare const APP_EVENT_V1_METADATA_KEY: "app_event";
5
+ export declare const APP_EVENT_V1_MAX_SUMMARY_LENGTH = 512;
6
+ export declare const APP_EVENT_V1_MAX_DATA_BYTES: number;
7
+ export declare const APP_EVENT_V1_MAX_REFS = 32;
8
+ export declare const APP_EVENT_V1_MAX_TARGETS = 16;
9
+ export type AppEventActorKind = "agent" | "human" | "service" | "model" | "workflow" | "system";
10
+ export type AppEventSensitivity = "public" | "internal" | "confidential" | "restricted";
11
+ export type AppEventRedactionState = "none" | "partial" | "full";
12
+ export type AppEventDeliveryIntent = "notification" | "state_sync" | "audit" | "command";
13
+ export type AppEventDeliveryMode = "at_most_once" | "at_least_once";
14
+ export interface AppEventSource {
15
+ app: string;
16
+ version: string;
17
+ machine: string;
18
+ }
19
+ export interface AppEventIdempotency {
20
+ dedupe_key: string;
21
+ replay_safe: boolean;
22
+ replay_of_event_id?: string;
23
+ }
24
+ export interface AppEventCorrelation {
25
+ correlation_id: string;
26
+ causation_id?: string;
27
+ trace_id?: string;
28
+ }
29
+ export interface AppEventSubject {
30
+ kind: string;
31
+ id: string;
32
+ uri?: string;
33
+ }
34
+ export interface AppEventActor {
35
+ kind: AppEventActorKind;
36
+ id: string;
37
+ name?: string;
38
+ }
39
+ export interface AppEventProjectMappings {
40
+ canonical_id: string;
41
+ slug?: string;
42
+ repository?: string;
43
+ workspace?: string;
44
+ external_ids: Record<string, string>;
45
+ }
46
+ /** Dependency-free structural mirror of a hasna.resource_ref pointer. */
47
+ export interface AppEventResourceRef {
48
+ kind: string;
49
+ id: string;
50
+ uri?: string;
51
+ source_package?: string;
52
+ external_id?: string;
53
+ }
54
+ /** Dependency-free structural mirror of a hasna.evidence_ref pointer. */
55
+ export interface AppEventEvidenceRef {
56
+ kind: string;
57
+ id: string;
58
+ uri: string;
59
+ sha256?: string;
60
+ redaction: AppEventRedactionState;
61
+ }
62
+ export interface AppEventSensitivityPolicy {
63
+ classification: AppEventSensitivity;
64
+ contains_personal_data: boolean;
65
+ }
66
+ export interface AppEventRedaction {
67
+ state: AppEventRedactionState;
68
+ fields: string[];
69
+ safe_for_logs: boolean;
70
+ }
71
+ export interface AppEventDelivery {
72
+ intent: AppEventDeliveryIntent;
73
+ mode: AppEventDeliveryMode;
74
+ targets: string[];
75
+ /** Untrusted event text is data, never an instruction to an agent. */
76
+ agent_conversation_injection: false;
77
+ }
78
+ /**
79
+ * Cross-app wire profile carried by the existing @hasna/events envelope.
80
+ * Use {@link appEventV1ToEventInput}; this profile does not define another bus.
81
+ */
82
+ export interface AppEventV1<TData extends EventData = EventData> {
83
+ event_id: string;
84
+ event_type: string;
85
+ schema_version: typeof APP_EVENT_V1_SCHEMA_VERSION;
86
+ source: AppEventSource;
87
+ occurred_at: string;
88
+ severity: EventSeverity;
89
+ idempotency: AppEventIdempotency;
90
+ correlation: AppEventCorrelation;
91
+ subject: AppEventSubject;
92
+ actor: AppEventActor;
93
+ project_mappings: AppEventProjectMappings;
94
+ summary: string;
95
+ data: TData;
96
+ resource_refs: AppEventResourceRef[];
97
+ evidence_refs: AppEventEvidenceRef[];
98
+ sensitivity: AppEventSensitivityPolicy;
99
+ redaction: AppEventRedaction;
100
+ delivery: AppEventDelivery;
101
+ }
102
+ export interface AppEventValidationIssue {
103
+ path: string;
104
+ message: string;
105
+ }
106
+ export type AppEventValidationResult = {
107
+ ok: true;
108
+ } | {
109
+ ok: false;
110
+ issues: AppEventValidationIssue[];
111
+ };
112
+ export declare class AppEventValidationError extends Error {
113
+ readonly issues: AppEventValidationIssue[];
114
+ constructor(issues: AppEventValidationIssue[]);
115
+ }
116
+ export declare class AppEventReplaySafetyError extends Error {
117
+ readonly eventId: string;
118
+ constructor(eventId: string);
119
+ }
120
+ export declare function validateAppEventV1(value: unknown): AppEventValidationResult;
121
+ export declare function assertAppEventV1<TData extends EventData = EventData>(value: unknown): asserts value is AppEventV1<TData>;
122
+ export declare function assertAppEventV1ReplaySafe(event: AppEventV1): void;
123
+ export declare function appEventV1ReplayIdentity(event: AppEventV1): {
124
+ eventId: string;
125
+ dedupeKey: string;
126
+ };
127
+ /** Map the profile onto the existing @hasna/events envelope without another transport or store. */
128
+ export declare function appEventV1ToEventInput<TData extends EventData>(event: AppEventV1<TData>): EventInput<TData>;
129
+ /** Reconstruct a profile from an @hasna/events envelope produced by the adapter. */
130
+ export declare function appEventV1FromEventEnvelope<TData extends EventData = EventData>(envelope: EventEnvelope<TData>): AppEventV1<TData>;
@@ -0,0 +1,30 @@
1
+ import type { EventEnvelope, EventInput } from "./types.js";
2
+ export interface DurableEventSpoolOptions {
3
+ dataDir: string;
4
+ }
5
+ export interface DurableSpoolEnqueueResult<TData extends Record<string, unknown> = Record<string, unknown>> {
6
+ event: EventEnvelope<TData>;
7
+ stored: boolean;
8
+ deduped: boolean;
9
+ }
10
+ export interface DurableSpoolRecoveryOptions {
11
+ olderThanMs?: number;
12
+ }
13
+ export interface DurableSpoolRecoveryResult {
14
+ recovered: number;
15
+ deduped: number;
16
+ cleaned: number;
17
+ }
18
+ export declare class DurableEventSpool {
19
+ readonly dataDir: string;
20
+ readonly inboxDir: string;
21
+ constructor(options: DurableEventSpoolOptions);
22
+ enqueue<TData extends Record<string, unknown>>(input: EventInput<TData>): Promise<DurableSpoolEnqueueResult<TData>>;
23
+ recover(options?: DurableSpoolRecoveryOptions): Promise<DurableSpoolRecoveryResult>;
24
+ close(): Promise<void>;
25
+ private pathFor;
26
+ private assertSameIdentity;
27
+ private ensureInbox;
28
+ private syncInbox;
29
+ private syncDirectory;
30
+ }
@@ -0,0 +1,27 @@
1
+ import { DurableEventsBroker, type DurableDrainResult, type DurableSpoolImportResult } from "./durable.js";
2
+ export interface DurableWorkerOptions {
3
+ broker: DurableEventsBroker;
4
+ signal: AbortSignal;
5
+ workerId?: string;
6
+ limit?: number;
7
+ leaseMs?: number;
8
+ debounceMs?: number;
9
+ reconcileMs?: number;
10
+ watchRestartMs?: number;
11
+ onCycle?: (cycle: DurableWorkerCycle) => void | Promise<void>;
12
+ }
13
+ export interface DurableWorkerCycle {
14
+ imported: DurableSpoolImportResult;
15
+ drained: DurableDrainResult;
16
+ }
17
+ export interface DurableWorkerResult {
18
+ workerId: string;
19
+ cycles: number;
20
+ imported: number;
21
+ deduped: number;
22
+ delivered: number;
23
+ retried: number;
24
+ dead: number;
25
+ lost: number;
26
+ }
27
+ export declare function runDurableWorker(options: DurableWorkerOptions): Promise<DurableWorkerResult>;
@@ -0,0 +1,112 @@
1
+ import { type TransportDispatchOptions } from "./transports.js";
2
+ import type { ChannelConfig, DeliveryResult, EventEnvelope, EventInput } from "./types.js";
3
+ export interface DurableEventsBrokerOptions extends TransportDispatchOptions {
4
+ dataDir: string;
5
+ databaseName?: string;
6
+ now?: () => Date;
7
+ }
8
+ export interface DurableEnqueueOptions {
9
+ dedupe?: boolean;
10
+ }
11
+ export interface DurableEnqueueResult<TData extends Record<string, unknown> = Record<string, unknown>> {
12
+ event: EventEnvelope<TData>;
13
+ deduped: boolean;
14
+ queued: number;
15
+ }
16
+ export interface DurableDrainOptions {
17
+ workerId?: string;
18
+ limit?: number;
19
+ leaseMs?: number;
20
+ }
21
+ export interface DurableDrainResult {
22
+ workerId: string;
23
+ claimed: number;
24
+ delivered: number;
25
+ retried: number;
26
+ dead: number;
27
+ lost: number;
28
+ deliveries: DeliveryResult[];
29
+ }
30
+ export interface DurableSpoolImportOptions {
31
+ limit?: number;
32
+ }
33
+ export interface DurableSpoolImportResult {
34
+ scanned: number;
35
+ imported: number;
36
+ deduped: number;
37
+ queued: number;
38
+ }
39
+ export interface DurableRetryDeadOptions {
40
+ eventId?: string;
41
+ channelId?: string;
42
+ limit?: number;
43
+ }
44
+ export interface DurableRetryDeadResult {
45
+ matched: number;
46
+ requeued: number;
47
+ }
48
+ export type DurableOutboxStatus = "pending" | "leased" | "delivered" | "dead";
49
+ export interface DurableEventsStatus {
50
+ service: "events";
51
+ storage: "local-sqlite";
52
+ schemaVersion: number;
53
+ databasePath: string;
54
+ counts: {
55
+ channels: number;
56
+ enabledChannels: number;
57
+ events: number;
58
+ pending: number;
59
+ leased: number;
60
+ delivered: number;
61
+ dead: number;
62
+ };
63
+ safety: {
64
+ statusOmitsEventPayloads: true;
65
+ databasePersistsEventEnvelopes: true;
66
+ includesResolvedSecrets: false;
67
+ inlineWebhookSecretsAllowed: false;
68
+ };
69
+ }
70
+ export interface DurableDeliveryJob {
71
+ id: string;
72
+ event: EventEnvelope;
73
+ channel: ChannelConfig;
74
+ attempt: number;
75
+ workerId: string;
76
+ }
77
+ export interface DurableSettleResult {
78
+ status: "delivered" | "retry" | "dead" | "lost";
79
+ delivery?: DeliveryResult;
80
+ }
81
+ export declare function defaultWebhookSecretResolver(reference: string): string | undefined;
82
+ export declare class DurableEventsBroker {
83
+ readonly dataDir: string;
84
+ readonly databasePath: string;
85
+ private readonly db;
86
+ private readonly now;
87
+ private readonly transportOptions;
88
+ constructor(options: DurableEventsBrokerOptions);
89
+ close(): void;
90
+ addChannel(input: Omit<ChannelConfig, "createdAt" | "updatedAt"> & Partial<Pick<ChannelConfig, "createdAt" | "updatedAt">>): ChannelConfig;
91
+ listChannels(): ChannelConfig[];
92
+ enqueue<TData extends Record<string, unknown>>(input: EventInput<TData>, options?: DurableEnqueueOptions): DurableEnqueueResult<TData>;
93
+ drain(options?: DurableDrainOptions): Promise<DurableDrainResult>;
94
+ importSpool(options?: DurableSpoolImportOptions): DurableSpoolImportResult;
95
+ retryDead(options?: DurableRetryDeadOptions): DurableRetryDeadResult;
96
+ status(): DurableEventsStatus;
97
+ nextWakeAt(): number | undefined;
98
+ private claim;
99
+ private settle;
100
+ private completeOutbox;
101
+ private findEvent;
102
+ private queueMatchingChannels;
103
+ private count;
104
+ private immediate;
105
+ private ensureSchema;
106
+ private createSchemaV1;
107
+ private assertSchemaV1;
108
+ private readSchemaVersion;
109
+ private applicationSchemaObjects;
110
+ private assertEmptyApplicationSchema;
111
+ private secureDatabaseFiles;
112
+ }
@@ -8,6 +8,8 @@ export * from "./filter.js";
8
8
  export * from "./signing.js";
9
9
  export * from "./transports.js";
10
10
  export * from "./catalog.js";
11
+ export * from "./app-event.js";
12
+ export { redactPaths, redactSensitiveKeys } from "./redaction.js";
11
13
  export interface EventsClientOptions extends TransportDispatchOptions {
12
14
  store?: EventsStore;
13
15
  dataDir?: string;
@@ -58,7 +60,5 @@ export declare class EventsClient {
58
60
  private applyRedaction;
59
61
  private deliverWithRetry;
60
62
  }
61
- export declare function redactPaths<T extends EventEnvelope>(event: T, paths: string[], replacement?: string): T;
62
63
  export declare function sanitizeChannelForOutput(channel: ChannelConfig): ChannelConfig;
63
64
  export declare function sanitizeChannelsForOutput(channels: ChannelConfig[]): ChannelConfig[];
64
- export declare function redactSensitiveKeys<T extends EventEnvelope>(event: T, replacement?: string): T;
@@ -0,0 +1,4 @@
1
+ import type { EventEnvelope } from "./types.js";
2
+ export declare function redactPaths<T extends EventEnvelope>(event: T, paths: string[], replacement?: string): T;
3
+ export declare function redactSensitiveKeys<T extends EventEnvelope>(event: T, replacement?: string): T;
4
+ export declare function shouldRedactKey(key: string): boolean;
@@ -1,8 +1,15 @@
1
1
  import type { ChannelConfig, DeliveryAttempt, DeliveryResult, EventEnvelope } from "./types.js";
2
2
  export interface TransportDispatchOptions {
3
3
  fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
4
+ secretResolver?: WebhookSecretResolver;
5
+ now?: () => Date;
4
6
  }
5
- export declare function buildWebhookRequest(event: EventEnvelope, channel: ChannelConfig): {
7
+ export type WebhookSecretResolver = (reference: string) => string | undefined | Promise<string | undefined>;
8
+ export interface BuildWebhookRequestOptions {
9
+ secret?: string;
10
+ timestamp?: string;
11
+ }
12
+ export declare function buildWebhookRequest(event: EventEnvelope, channel: ChannelConfig, options?: BuildWebhookRequestOptions): {
6
13
  body: string;
7
14
  headers: Record<string, string>;
8
15
  };
@@ -51,7 +51,16 @@ export interface RedactionConfig {
51
51
  }
52
52
  export interface WebhookTransportConfig {
53
53
  url: string;
54
+ /**
55
+ * Legacy inline HMAC secret. Durable stores should reject this field so
56
+ * credential material is never persisted with channel configuration.
57
+ */
54
58
  secret?: string;
59
+ /**
60
+ * Runtime-only HMAC secret reference, for example
61
+ * `env:HASNA_NOTES_WEBHOOK_SECRET`.
62
+ */
63
+ secretRef?: string;
55
64
  headers?: Record<string, string>;
56
65
  timeoutMs?: number;
57
66
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes