@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.
- package/LICENSE +198 -13
- package/README.md +184 -28
- package/dist/app-event.js +382 -0
- package/dist/catalog.js +12 -12
- package/dist/cli/index.js +1415 -80
- package/dist/commander.js +452 -49
- package/dist/durable-spool.js +184 -0
- package/dist/durable-worker.js +378 -0
- package/dist/durable.js +2232 -0
- package/dist/filter.js +2 -2
- package/dist/index.js +508 -91
- package/dist/signing.js +5 -5
- package/dist/storage.js +12 -12
- package/dist/transports.js +40 -11
- package/fixtures/hasna.app_event.v1.json +108 -0
- package/hasna.contract.json +70 -0
- package/package.json +43 -13
- package/schemas/hasna.app_event.v1.json +186 -0
- package/types/app-event.d.ts +130 -0
- package/types/durable-spool.d.ts +30 -0
- package/types/durable-worker.d.ts +27 -0
- package/types/durable.d.ts +112 -0
- package/{dist → types}/index.d.ts +2 -2
- package/types/redaction.d.ts +4 -0
- package/{dist → types}/transports.d.ts +8 -1
- package/{dist → types}/types.d.ts +9 -0
- /package/{dist → types}/catalog.d.ts +0 -0
- /package/{dist → types}/cli/index.d.ts +0 -0
- /package/{dist → types}/commander.d.ts +0 -0
- /package/{dist → types}/filter-options.d.ts +0 -0
- /package/{dist → types}/filter.d.ts +0 -0
- /package/{dist → types}/signing.d.ts +0 -0
- /package/{dist → types}/storage.d.ts +0 -0
package/dist/storage.js
CHANGED
|
@@ -304,17 +304,17 @@ function statusFile(dataDir, fileName, records) {
|
|
|
304
304
|
return { path, exists: existsSync(path), records };
|
|
305
305
|
}
|
|
306
306
|
export {
|
|
307
|
-
|
|
308
|
-
localJsonRuntime,
|
|
309
|
-
getEventsStatus,
|
|
310
|
-
getEventsDataDir,
|
|
311
|
-
getActiveEventsDirEnv,
|
|
312
|
-
encodeLocalJsonEventCursor,
|
|
313
|
-
decodeLocalJsonEventCursor,
|
|
314
|
-
MAX_EVENT_PAGE_LIMIT,
|
|
315
|
-
LOCAL_JSON_EVENT_CURSOR_PREFIX,
|
|
316
|
-
JsonEventsStore,
|
|
317
|
-
HASNA_EVENTS_HOME_ENV,
|
|
307
|
+
DEFAULT_EVENT_PAGE_LIMIT,
|
|
318
308
|
HASNA_EVENTS_DIR_ENV,
|
|
319
|
-
|
|
309
|
+
HASNA_EVENTS_HOME_ENV,
|
|
310
|
+
JsonEventsStore,
|
|
311
|
+
LOCAL_JSON_EVENT_CURSOR_PREFIX,
|
|
312
|
+
MAX_EVENT_PAGE_LIMIT,
|
|
313
|
+
decodeLocalJsonEventCursor,
|
|
314
|
+
encodeLocalJsonEventCursor,
|
|
315
|
+
getActiveEventsDirEnv,
|
|
316
|
+
getEventsDataDir,
|
|
317
|
+
getEventsStatus,
|
|
318
|
+
localJsonRuntime,
|
|
319
|
+
normalizeEventPageLimit
|
|
320
320
|
};
|
package/dist/transports.js
CHANGED
|
@@ -42,21 +42,27 @@ function now() {
|
|
|
42
42
|
function truncate(value, max = 4096) {
|
|
43
43
|
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
44
44
|
}
|
|
45
|
-
function buildWebhookRequest(event, channel) {
|
|
45
|
+
function buildWebhookRequest(event, channel, options = {}) {
|
|
46
46
|
if (!channel.webhook)
|
|
47
47
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
48
|
+
for (const name of Object.keys(channel.webhook.headers ?? {})) {
|
|
49
|
+
if (/^x-hasna-/i.test(name)) {
|
|
50
|
+
throw new Error(`Webhook header ${name} is reserved for signed delivery metadata`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
48
53
|
const body = JSON.stringify(event);
|
|
49
|
-
const timestamp =
|
|
54
|
+
const timestamp = options.timestamp ?? new Date().toISOString();
|
|
50
55
|
const headers = {
|
|
51
56
|
"Content-Type": "application/json",
|
|
52
57
|
"User-Agent": "@hasna/events",
|
|
53
58
|
"X-Hasna-Event-Id": event.id,
|
|
54
59
|
"X-Hasna-Event-Type": event.type,
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
...channel.webhook.headers,
|
|
61
|
+
"X-Hasna-Timestamp": timestamp
|
|
57
62
|
};
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
const secret = options.secret ?? channel.webhook.secret;
|
|
64
|
+
if (secret) {
|
|
65
|
+
headers["X-Hasna-Signature"] = signPayload(secret, timestamp, body);
|
|
60
66
|
}
|
|
61
67
|
return { body, headers };
|
|
62
68
|
}
|
|
@@ -64,7 +70,21 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
64
70
|
if (!channel.webhook)
|
|
65
71
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
66
72
|
const startedAt = now();
|
|
67
|
-
|
|
73
|
+
let secret = channel.webhook.secret;
|
|
74
|
+
if (channel.webhook.secretRef) {
|
|
75
|
+
if (!options.secretResolver) {
|
|
76
|
+
return failedAttempt(startedAt, "Webhook secret reference has no runtime resolver");
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
secret = await options.secretResolver(channel.webhook.secretRef);
|
|
80
|
+
} catch {
|
|
81
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
82
|
+
}
|
|
83
|
+
if (!secret)
|
|
84
|
+
return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
|
|
85
|
+
}
|
|
86
|
+
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
87
|
+
const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
|
|
68
88
|
const controller = new AbortController;
|
|
69
89
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
70
90
|
try {
|
|
@@ -96,6 +116,15 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
96
116
|
clearTimeout(timeout);
|
|
97
117
|
}
|
|
98
118
|
}
|
|
119
|
+
function failedAttempt(startedAt, error) {
|
|
120
|
+
return {
|
|
121
|
+
attempt: 1,
|
|
122
|
+
status: "failed",
|
|
123
|
+
startedAt,
|
|
124
|
+
completedAt: now(),
|
|
125
|
+
error
|
|
126
|
+
};
|
|
127
|
+
}
|
|
99
128
|
async function dispatchCommand(event, channel) {
|
|
100
129
|
if (!channel.command)
|
|
101
130
|
throw new Error(`Channel ${channel.id} has no command config`);
|
|
@@ -185,9 +214,9 @@ function createDeliveryResult(event, channel, attempts) {
|
|
|
185
214
|
};
|
|
186
215
|
}
|
|
187
216
|
export {
|
|
188
|
-
|
|
189
|
-
dispatchCommand,
|
|
190
|
-
dispatchChannel,
|
|
217
|
+
buildWebhookRequest,
|
|
191
218
|
createDeliveryResult,
|
|
192
|
-
|
|
219
|
+
dispatchChannel,
|
|
220
|
+
dispatchCommand,
|
|
221
|
+
dispatchWebhook
|
|
193
222
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"event_id": "evt_fixture_todo_completed_01",
|
|
3
|
+
"event_type": "todos.task.completed",
|
|
4
|
+
"schema_version": "hasna.app_event.v1",
|
|
5
|
+
"source": {
|
|
6
|
+
"app": "todos",
|
|
7
|
+
"version": "0.0.0-fixture",
|
|
8
|
+
"machine": "fixture.local"
|
|
9
|
+
},
|
|
10
|
+
"occurred_at": "2026-07-29T12:00:00.000Z",
|
|
11
|
+
"severity": "notice",
|
|
12
|
+
"idempotency": {
|
|
13
|
+
"dedupe_key": "fixture:interop-project:todo-42:completed:v1",
|
|
14
|
+
"replay_safe": true
|
|
15
|
+
},
|
|
16
|
+
"correlation": {
|
|
17
|
+
"correlation_id": "corr_fixture_interop_project_01",
|
|
18
|
+
"causation_id": "evt_fixture_todo_created_01"
|
|
19
|
+
},
|
|
20
|
+
"subject": {
|
|
21
|
+
"kind": "task",
|
|
22
|
+
"id": "todo_fixture_42",
|
|
23
|
+
"uri": "hasna://todos/tasks/todo_fixture_42"
|
|
24
|
+
},
|
|
25
|
+
"actor": {
|
|
26
|
+
"kind": "service",
|
|
27
|
+
"id": "todos",
|
|
28
|
+
"name": "Todos fixture producer"
|
|
29
|
+
},
|
|
30
|
+
"project_mappings": {
|
|
31
|
+
"canonical_id": "project_fixture_interop",
|
|
32
|
+
"slug": "interop-fixture",
|
|
33
|
+
"repository": "https://example.invalid/hasna/interop-fixture",
|
|
34
|
+
"workspace": "workspace_fixture_interop",
|
|
35
|
+
"external_ids": {
|
|
36
|
+
"todos": "project_fixture_interop",
|
|
37
|
+
"conversations": "conversation_fixture_17",
|
|
38
|
+
"mementos": "memento_fixture_09",
|
|
39
|
+
"projects": "project_fixture_interop",
|
|
40
|
+
"codewith": "mailbox_fixture_interop"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"summary": "Interop fixture task completed successfully.",
|
|
44
|
+
"data": {
|
|
45
|
+
"task_status": "completed",
|
|
46
|
+
"task_title": "Verify app-event interoperability",
|
|
47
|
+
"conversation_id": "conversation_fixture_17",
|
|
48
|
+
"memento_id": "memento_fixture_09",
|
|
49
|
+
"mailbox_topic": "project.activity",
|
|
50
|
+
"render_hint": {
|
|
51
|
+
"component": "StatusBadge",
|
|
52
|
+
"tone": "success"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"resource_refs": [
|
|
56
|
+
{
|
|
57
|
+
"kind": "task",
|
|
58
|
+
"id": "todo_fixture_42",
|
|
59
|
+
"uri": "hasna://todos/tasks/todo_fixture_42",
|
|
60
|
+
"source_package": "@hasna/todos",
|
|
61
|
+
"external_id": "todo_fixture_42"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"kind": "conversation",
|
|
65
|
+
"id": "conversation_fixture_17",
|
|
66
|
+
"uri": "hasna://conversations/conversation_fixture_17"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"kind": "memento",
|
|
70
|
+
"id": "memento_fixture_09",
|
|
71
|
+
"uri": "hasna://mementos/memento_fixture_09"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"kind": "render",
|
|
75
|
+
"id": "project_fixture_interop_status",
|
|
76
|
+
"uri": "hasna://projects/project_fixture_interop/render/status"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"evidence_refs": [
|
|
80
|
+
{
|
|
81
|
+
"kind": "test_result",
|
|
82
|
+
"id": "evidence_fixture_interop_01",
|
|
83
|
+
"uri": "artifact://interop-fixture/test-report.json",
|
|
84
|
+
"redaction": "none"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"sensitivity": {
|
|
88
|
+
"classification": "internal",
|
|
89
|
+
"contains_personal_data": false
|
|
90
|
+
},
|
|
91
|
+
"redaction": {
|
|
92
|
+
"state": "none",
|
|
93
|
+
"fields": [],
|
|
94
|
+
"safe_for_logs": true
|
|
95
|
+
},
|
|
96
|
+
"delivery": {
|
|
97
|
+
"intent": "notification",
|
|
98
|
+
"mode": "at_least_once",
|
|
99
|
+
"targets": [
|
|
100
|
+
"todos",
|
|
101
|
+
"conversations",
|
|
102
|
+
"mementos",
|
|
103
|
+
"projects-json-render",
|
|
104
|
+
"codewith-mailbox"
|
|
105
|
+
],
|
|
106
|
+
"agent_conversation_injection": false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "hasna.service_contract.v1",
|
|
3
|
+
"name": "events",
|
|
4
|
+
"class": "library",
|
|
5
|
+
"contractVersion": "v1",
|
|
6
|
+
"kitVersion": "0.8.5",
|
|
7
|
+
"description": "Shared event envelopes, local channels, replay, and delivery transports for Hasna open-source apps. Library class: SDK exports are the primary surface and the local `events` CLI is the operator seam. The default compatibility store remains user-owned JSON; opt-in durable delivery uses a Node-safe filesystem spool plus a Bun SQLite outbox under the same caller-selected data directory. The package provisions no remote infrastructure.",
|
|
8
|
+
"bins": [
|
|
9
|
+
"events"
|
|
10
|
+
],
|
|
11
|
+
"hosting": [
|
|
12
|
+
"user-hosted"
|
|
13
|
+
],
|
|
14
|
+
"serviceSurfaces": [
|
|
15
|
+
{
|
|
16
|
+
"name": "package-sdk",
|
|
17
|
+
"kind": "sdk",
|
|
18
|
+
"status": "supported",
|
|
19
|
+
"authMode": "none",
|
|
20
|
+
"exportSubpath": "."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "durable-spool-sdk",
|
|
24
|
+
"kind": "sdk",
|
|
25
|
+
"status": "supported",
|
|
26
|
+
"authMode": "none",
|
|
27
|
+
"exportSubpath": "./durable-spool"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "durable-delivery-sdk",
|
|
31
|
+
"kind": "sdk",
|
|
32
|
+
"status": "supported",
|
|
33
|
+
"authMode": "none",
|
|
34
|
+
"exportSubpath": "./durable"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "durable-worker-sdk",
|
|
38
|
+
"kind": "sdk",
|
|
39
|
+
"status": "supported",
|
|
40
|
+
"authMode": "none",
|
|
41
|
+
"exportSubpath": "./durable-worker"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "events-cli",
|
|
45
|
+
"kind": "cli",
|
|
46
|
+
"status": "supported",
|
|
47
|
+
"bin": "events",
|
|
48
|
+
"authMode": "local-only"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"metadata": {
|
|
52
|
+
"release": {
|
|
53
|
+
"artifactScan": {
|
|
54
|
+
"script": "artifact-scan"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"conformance": {
|
|
58
|
+
"waivedSurfaces": [
|
|
59
|
+
{
|
|
60
|
+
"kind": "api",
|
|
61
|
+
"reason": "Embedded library plus a local CLI; it owns no HTTP service boundary and ships no serve bin."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"kind": "mcp",
|
|
65
|
+
"reason": "Embedded library; MCP execution belongs to the applications that consume these envelopes."
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,57 +1,85 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/events",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Shared event envelopes, local channels, replay, and delivery transports for Hasna open-source apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
|
-
"types": "./
|
|
7
|
+
"types": "./types/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
9
|
"events": "dist/cli/index.js",
|
|
10
10
|
"hasna-events": "dist/cli/index.js"
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"types": "./
|
|
14
|
+
"types": "./types/index.d.ts",
|
|
15
15
|
"import": "./dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"./storage": {
|
|
18
|
-
"types": "./
|
|
18
|
+
"types": "./types/storage.d.ts",
|
|
19
19
|
"import": "./dist/storage.js"
|
|
20
20
|
},
|
|
21
|
+
"./durable": {
|
|
22
|
+
"types": "./types/durable.d.ts",
|
|
23
|
+
"import": "./dist/durable.js"
|
|
24
|
+
},
|
|
25
|
+
"./durable-spool": {
|
|
26
|
+
"types": "./types/durable-spool.d.ts",
|
|
27
|
+
"import": "./dist/durable-spool.js"
|
|
28
|
+
},
|
|
29
|
+
"./durable-worker": {
|
|
30
|
+
"types": "./types/durable-worker.d.ts",
|
|
31
|
+
"import": "./dist/durable-worker.js"
|
|
32
|
+
},
|
|
21
33
|
"./signing": {
|
|
22
|
-
"types": "./
|
|
34
|
+
"types": "./types/signing.d.ts",
|
|
23
35
|
"import": "./dist/signing.js"
|
|
24
36
|
},
|
|
25
37
|
"./filter": {
|
|
26
|
-
"types": "./
|
|
38
|
+
"types": "./types/filter.d.ts",
|
|
27
39
|
"import": "./dist/filter.js"
|
|
28
40
|
},
|
|
29
41
|
"./transports": {
|
|
30
|
-
"types": "./
|
|
42
|
+
"types": "./types/transports.d.ts",
|
|
31
43
|
"import": "./dist/transports.js"
|
|
32
44
|
},
|
|
33
45
|
"./catalog": {
|
|
34
|
-
"types": "./
|
|
46
|
+
"types": "./types/catalog.d.ts",
|
|
35
47
|
"import": "./dist/catalog.js"
|
|
36
48
|
},
|
|
49
|
+
"./app-event": {
|
|
50
|
+
"types": "./types/app-event.d.ts",
|
|
51
|
+
"import": "./dist/app-event.js"
|
|
52
|
+
},
|
|
53
|
+
"./schemas/hasna.app_event.v1.json": "./schemas/hasna.app_event.v1.json",
|
|
54
|
+
"./fixtures/hasna.app_event.v1.json": "./fixtures/hasna.app_event.v1.json",
|
|
37
55
|
"./commander": {
|
|
38
|
-
"types": "./
|
|
56
|
+
"types": "./types/commander.d.ts",
|
|
39
57
|
"import": "./dist/commander.js"
|
|
40
58
|
},
|
|
41
59
|
"./cli": {
|
|
42
|
-
"types": "./
|
|
60
|
+
"types": "./types/cli/index.d.ts",
|
|
43
61
|
"import": "./dist/cli/index.js"
|
|
44
62
|
}
|
|
45
63
|
},
|
|
46
64
|
"files": [
|
|
47
65
|
"dist",
|
|
66
|
+
"types",
|
|
67
|
+
"schemas",
|
|
68
|
+
"fixtures",
|
|
48
69
|
"README.md",
|
|
49
|
-
"LICENSE"
|
|
70
|
+
"LICENSE",
|
|
71
|
+
"hasna.contract.json"
|
|
50
72
|
],
|
|
51
73
|
"scripts": {
|
|
52
|
-
"build": "
|
|
74
|
+
"build": "bun run build:runtime && bun run build:types",
|
|
75
|
+
"build:runtime": "rm -rf dist && bun build src/cli/index.ts --outdir dist/cli --target bun && bun build src/index.ts src/storage.ts src/signing.ts src/filter.ts src/transports.ts src/types.ts src/commander.ts src/catalog.ts src/app-event.ts src/durable.ts src/durable-worker.ts --root src --outdir dist --target bun && bun build src/durable-spool.ts --root src --outdir dist --target node",
|
|
76
|
+
"build:types": "rm -rf types && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir types",
|
|
77
|
+
"generated-artifacts:check": "bun run build && test -z \"$(git status --porcelain --untracked-files=all -- dist types)\"",
|
|
78
|
+
"contract:check": "contracts repo-conformance .",
|
|
79
|
+
"artifact-scan": "bun scripts/artifact-scan.ts",
|
|
53
80
|
"typecheck": "tsc --noEmit",
|
|
54
81
|
"test": "bun test",
|
|
82
|
+
"prepack": "bun run build && bun run artifact-scan",
|
|
55
83
|
"prepublishOnly": "bun run test && bun run build"
|
|
56
84
|
},
|
|
57
85
|
"keywords": [
|
|
@@ -75,7 +103,8 @@
|
|
|
75
103
|
"url": "https://github.com/hasna/events/issues"
|
|
76
104
|
},
|
|
77
105
|
"engines": {
|
|
78
|
-
"bun": ">=1.0.0"
|
|
106
|
+
"bun": ">=1.0.0",
|
|
107
|
+
"node": ">=20.0.0"
|
|
79
108
|
},
|
|
80
109
|
"author": "Hasna",
|
|
81
110
|
"license": "Apache-2.0",
|
|
@@ -83,6 +112,7 @@
|
|
|
83
112
|
"commander": "^13.1.0"
|
|
84
113
|
},
|
|
85
114
|
"devDependencies": {
|
|
115
|
+
"@hasna/contracts": "0.8.5",
|
|
86
116
|
"@types/bun": "latest",
|
|
87
117
|
"typescript": "^5.7.3"
|
|
88
118
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.hasna.com/hasna.app_event.v1.json",
|
|
4
|
+
"title": "hasna.app_event.v1",
|
|
5
|
+
"description": "Versioned cross-app event profile carried by @hasna/events.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"event_id",
|
|
10
|
+
"event_type",
|
|
11
|
+
"schema_version",
|
|
12
|
+
"source",
|
|
13
|
+
"occurred_at",
|
|
14
|
+
"severity",
|
|
15
|
+
"idempotency",
|
|
16
|
+
"correlation",
|
|
17
|
+
"subject",
|
|
18
|
+
"actor",
|
|
19
|
+
"project_mappings",
|
|
20
|
+
"summary",
|
|
21
|
+
"data",
|
|
22
|
+
"resource_refs",
|
|
23
|
+
"evidence_refs",
|
|
24
|
+
"sensitivity",
|
|
25
|
+
"redaction",
|
|
26
|
+
"delivery"
|
|
27
|
+
],
|
|
28
|
+
"properties": {
|
|
29
|
+
"event_id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
30
|
+
"event_type": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
31
|
+
"schema_version": { "const": "hasna.app_event.v1" },
|
|
32
|
+
"source": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"required": ["app", "version", "machine"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"app": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
38
|
+
"version": { "type": "string", "minLength": 1, "maxLength": 100 },
|
|
39
|
+
"machine": { "type": "string", "minLength": 1, "maxLength": 200 }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"occurred_at": { "type": "string", "format": "date-time" },
|
|
43
|
+
"severity": { "enum": ["debug", "info", "notice", "warning", "error", "critical"] },
|
|
44
|
+
"idempotency": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"required": ["dedupe_key", "replay_safe"],
|
|
48
|
+
"properties": {
|
|
49
|
+
"dedupe_key": { "type": "string", "minLength": 1, "maxLength": 512 },
|
|
50
|
+
"replay_safe": { "type": "boolean" },
|
|
51
|
+
"replay_of_event_id": { "type": "string", "minLength": 1, "maxLength": 200 }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"correlation": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"required": ["correlation_id"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"correlation_id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
60
|
+
"causation_id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
61
|
+
"trace_id": { "type": "string", "minLength": 1, "maxLength": 200 }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"subject": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"required": ["kind", "id"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"kind": { "type": "string", "minLength": 1, "maxLength": 100 },
|
|
70
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
71
|
+
"uri": { "type": "string", "minLength": 1, "maxLength": 2048, "format": "uri-reference" }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"actor": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"additionalProperties": false,
|
|
77
|
+
"required": ["kind", "id"],
|
|
78
|
+
"properties": {
|
|
79
|
+
"kind": { "enum": ["agent", "human", "service", "model", "workflow", "system"] },
|
|
80
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
81
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 200 }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"project_mappings": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"additionalProperties": false,
|
|
87
|
+
"required": ["canonical_id", "external_ids"],
|
|
88
|
+
"properties": {
|
|
89
|
+
"canonical_id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
90
|
+
"slug": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
91
|
+
"repository": { "type": "string", "minLength": 1, "maxLength": 2048, "format": "uri-reference" },
|
|
92
|
+
"workspace": { "type": "string", "minLength": 1, "maxLength": 2048 },
|
|
93
|
+
"external_ids": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"maxProperties": 16,
|
|
96
|
+
"propertyNames": { "minLength": 1 },
|
|
97
|
+
"additionalProperties": { "type": "string", "minLength": 1 }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"summary": { "type": "string", "minLength": 1, "maxLength": 512 },
|
|
102
|
+
"data": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"description": "Bounded to 32768 serialized UTF-8 bytes by the runtime validator.",
|
|
105
|
+
"x-max-utf8-bytes": 32768
|
|
106
|
+
},
|
|
107
|
+
"resource_refs": {
|
|
108
|
+
"type": "array",
|
|
109
|
+
"maxItems": 32,
|
|
110
|
+
"items": { "$ref": "#/$defs/resource_ref" }
|
|
111
|
+
},
|
|
112
|
+
"evidence_refs": {
|
|
113
|
+
"type": "array",
|
|
114
|
+
"maxItems": 32,
|
|
115
|
+
"items": { "$ref": "#/$defs/evidence_ref" }
|
|
116
|
+
},
|
|
117
|
+
"sensitivity": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"additionalProperties": false,
|
|
120
|
+
"required": ["classification", "contains_personal_data"],
|
|
121
|
+
"properties": {
|
|
122
|
+
"classification": { "enum": ["public", "internal", "confidential", "restricted"] },
|
|
123
|
+
"contains_personal_data": { "type": "boolean" }
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"redaction": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"additionalProperties": false,
|
|
129
|
+
"required": ["state", "fields", "safe_for_logs"],
|
|
130
|
+
"properties": {
|
|
131
|
+
"state": { "enum": ["none", "partial", "full"] },
|
|
132
|
+
"fields": {
|
|
133
|
+
"type": "array",
|
|
134
|
+
"maxItems": 32,
|
|
135
|
+
"items": { "type": "string", "minLength": 1 }
|
|
136
|
+
},
|
|
137
|
+
"safe_for_logs": { "type": "boolean" }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"delivery": {
|
|
141
|
+
"type": "object",
|
|
142
|
+
"additionalProperties": false,
|
|
143
|
+
"required": ["intent", "mode", "targets", "agent_conversation_injection"],
|
|
144
|
+
"properties": {
|
|
145
|
+
"intent": { "enum": ["notification", "state_sync", "audit", "command"] },
|
|
146
|
+
"mode": { "enum": ["at_most_once", "at_least_once"] },
|
|
147
|
+
"targets": {
|
|
148
|
+
"type": "array",
|
|
149
|
+
"minItems": 1,
|
|
150
|
+
"maxItems": 16,
|
|
151
|
+
"items": { "type": "string", "minLength": 1 }
|
|
152
|
+
},
|
|
153
|
+
"agent_conversation_injection": {
|
|
154
|
+
"const": false,
|
|
155
|
+
"description": "Event text is untrusted data and must not be injected into agent conversations."
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"$defs": {
|
|
161
|
+
"resource_ref": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"additionalProperties": false,
|
|
164
|
+
"required": ["kind", "id"],
|
|
165
|
+
"properties": {
|
|
166
|
+
"kind": { "type": "string", "minLength": 1, "maxLength": 100 },
|
|
167
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
168
|
+
"uri": { "type": "string", "minLength": 1, "maxLength": 2048, "format": "uri-reference" },
|
|
169
|
+
"source_package": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
170
|
+
"external_id": { "type": "string", "minLength": 1, "maxLength": 200 }
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"evidence_ref": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"additionalProperties": false,
|
|
176
|
+
"required": ["kind", "id", "uri", "redaction"],
|
|
177
|
+
"properties": {
|
|
178
|
+
"kind": { "type": "string", "minLength": 1, "maxLength": 100 },
|
|
179
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
180
|
+
"uri": { "type": "string", "minLength": 1, "maxLength": 2048, "format": "uri-reference" },
|
|
181
|
+
"sha256": { "type": "string", "pattern": "^[a-fA-F0-9]{64}$" },
|
|
182
|
+
"redaction": { "enum": ["none", "partial", "full"] }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|