@open-everywhere/provider-notion 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +48 -0
- package/agent/channels/notion-acting-identity.ts +88 -0
- package/agent/channels/notion-ancestry-trust.ts +458 -0
- package/agent/channels/notion-api.ts +1290 -0
- package/agent/channels/notion-bot-user.ts +250 -0
- package/agent/channels/notion-catch-up.ts +339 -0
- package/agent/channels/notion-credentials.ts +417 -0
- package/agent/channels/notion-engagement.ts +372 -0
- package/agent/channels/notion-event-normalizer.ts +587 -0
- package/agent/channels/notion-identity.ts +271 -0
- package/agent/channels/notion-ingress-drain.ts +289 -0
- package/agent/channels/notion-ingress.ts +182 -0
- package/agent/channels/notion-message-ops.ts +140 -0
- package/agent/channels/notion-origin-codec.ts +38 -0
- package/agent/channels/notion-post-arm.ts +189 -0
- package/agent/channels/notion-question-return.ts +223 -0
- package/agent/channels/notion-read.ts +283 -0
- package/agent/channels/notion-scope.ts +560 -0
- package/agent/channels/notion-stop.ts +169 -0
- package/agent/channels/notion-supervisor.ts +856 -0
- package/agent/channels/notion-webhook-timing.ts +131 -0
- package/agent/channels/notion-webhook-verifier.ts +55 -0
- package/agent/channels/notion-webhook.ts +963 -0
- package/agent/channels/notion-work-search.ts +74 -0
- package/agent/channels/notion-work.ts +144 -0
- package/agent/channels/notion.ts +1242 -0
- package/agent/contributions.ts +39 -0
- package/agent/docs-generated.ts +15 -0
- package/agent/memory/blocks.ts +295 -0
- package/agent/memory/memory.ts +622 -0
- package/agent/provider-events.ts +198 -0
- package/agent/register.ts +144 -0
- package/agent/schedules/notion-ingress.ts +20 -0
- package/agent/surface-parity.ts +105 -0
- package/agent/tools/notion_search.ts +110 -0
- package/agent/tools/read_notion_comments.ts +250 -0
- package/agent/tools/read_notion_page.ts +351 -0
- package/dist/agent/channels/notion-acting-identity.d.ts +23 -0
- package/dist/agent/channels/notion-acting-identity.d.ts.map +1 -0
- package/dist/agent/channels/notion-acting-identity.js +85 -0
- package/dist/agent/channels/notion-acting-identity.js.map +1 -0
- package/dist/agent/channels/notion-ancestry-trust.d.ts +208 -0
- package/dist/agent/channels/notion-ancestry-trust.d.ts.map +1 -0
- package/dist/agent/channels/notion-ancestry-trust.js +331 -0
- package/dist/agent/channels/notion-ancestry-trust.js.map +1 -0
- package/dist/agent/channels/notion-api.d.ts +655 -0
- package/dist/agent/channels/notion-api.d.ts.map +1 -0
- package/dist/agent/channels/notion-api.js +938 -0
- package/dist/agent/channels/notion-api.js.map +1 -0
- package/dist/agent/channels/notion-bot-user.d.ts +107 -0
- package/dist/agent/channels/notion-bot-user.d.ts.map +1 -0
- package/dist/agent/channels/notion-bot-user.js +192 -0
- package/dist/agent/channels/notion-bot-user.js.map +1 -0
- package/dist/agent/channels/notion-catch-up.d.ts +64 -0
- package/dist/agent/channels/notion-catch-up.d.ts.map +1 -0
- package/dist/agent/channels/notion-catch-up.js +289 -0
- package/dist/agent/channels/notion-catch-up.js.map +1 -0
- package/dist/agent/channels/notion-credentials.d.ts +210 -0
- package/dist/agent/channels/notion-credentials.d.ts.map +1 -0
- package/dist/agent/channels/notion-credentials.js +360 -0
- package/dist/agent/channels/notion-credentials.js.map +1 -0
- package/dist/agent/channels/notion-engagement.d.ts +219 -0
- package/dist/agent/channels/notion-engagement.d.ts.map +1 -0
- package/dist/agent/channels/notion-engagement.js +283 -0
- package/dist/agent/channels/notion-engagement.js.map +1 -0
- package/dist/agent/channels/notion-event-normalizer.d.ts +194 -0
- package/dist/agent/channels/notion-event-normalizer.d.ts.map +1 -0
- package/dist/agent/channels/notion-event-normalizer.js +542 -0
- package/dist/agent/channels/notion-event-normalizer.js.map +1 -0
- package/dist/agent/channels/notion-identity.d.ts +119 -0
- package/dist/agent/channels/notion-identity.d.ts.map +1 -0
- package/dist/agent/channels/notion-identity.js +205 -0
- package/dist/agent/channels/notion-identity.js.map +1 -0
- package/dist/agent/channels/notion-ingress-drain.d.ts +66 -0
- package/dist/agent/channels/notion-ingress-drain.d.ts.map +1 -0
- package/dist/agent/channels/notion-ingress-drain.js +185 -0
- package/dist/agent/channels/notion-ingress-drain.js.map +1 -0
- package/dist/agent/channels/notion-ingress.d.ts +64 -0
- package/dist/agent/channels/notion-ingress.d.ts.map +1 -0
- package/dist/agent/channels/notion-ingress.js +130 -0
- package/dist/agent/channels/notion-ingress.js.map +1 -0
- package/dist/agent/channels/notion-message-ops.d.ts +3 -0
- package/dist/agent/channels/notion-message-ops.d.ts.map +1 -0
- package/dist/agent/channels/notion-message-ops.js +126 -0
- package/dist/agent/channels/notion-message-ops.js.map +1 -0
- package/dist/agent/channels/notion-origin-codec.d.ts +7 -0
- package/dist/agent/channels/notion-origin-codec.d.ts.map +1 -0
- package/dist/agent/channels/notion-origin-codec.js +32 -0
- package/dist/agent/channels/notion-origin-codec.js.map +1 -0
- package/dist/agent/channels/notion-post-arm.d.ts +3 -0
- package/dist/agent/channels/notion-post-arm.d.ts.map +1 -0
- package/dist/agent/channels/notion-post-arm.js +185 -0
- package/dist/agent/channels/notion-post-arm.js.map +1 -0
- package/dist/agent/channels/notion-question-return.d.ts +77 -0
- package/dist/agent/channels/notion-question-return.d.ts.map +1 -0
- package/dist/agent/channels/notion-question-return.js +183 -0
- package/dist/agent/channels/notion-question-return.js.map +1 -0
- package/dist/agent/channels/notion-read.d.ts +62 -0
- package/dist/agent/channels/notion-read.d.ts.map +1 -0
- package/dist/agent/channels/notion-read.js +257 -0
- package/dist/agent/channels/notion-read.js.map +1 -0
- package/dist/agent/channels/notion-scope.d.ts +188 -0
- package/dist/agent/channels/notion-scope.d.ts.map +1 -0
- package/dist/agent/channels/notion-scope.js +433 -0
- package/dist/agent/channels/notion-scope.js.map +1 -0
- package/dist/agent/channels/notion-stop.d.ts +48 -0
- package/dist/agent/channels/notion-stop.d.ts.map +1 -0
- package/dist/agent/channels/notion-stop.js +141 -0
- package/dist/agent/channels/notion-stop.js.map +1 -0
- package/dist/agent/channels/notion-supervisor.d.ts +462 -0
- package/dist/agent/channels/notion-supervisor.d.ts.map +1 -0
- package/dist/agent/channels/notion-supervisor.js +482 -0
- package/dist/agent/channels/notion-supervisor.js.map +1 -0
- package/dist/agent/channels/notion-webhook-timing.d.ts +35 -0
- package/dist/agent/channels/notion-webhook-timing.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook-timing.js +99 -0
- package/dist/agent/channels/notion-webhook-timing.js.map +1 -0
- package/dist/agent/channels/notion-webhook-verifier.d.ts +5 -0
- package/dist/agent/channels/notion-webhook-verifier.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook-verifier.js +53 -0
- package/dist/agent/channels/notion-webhook-verifier.js.map +1 -0
- package/dist/agent/channels/notion-webhook.d.ts +242 -0
- package/dist/agent/channels/notion-webhook.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook.js +675 -0
- package/dist/agent/channels/notion-webhook.js.map +1 -0
- package/dist/agent/channels/notion-work-search.d.ts +9 -0
- package/dist/agent/channels/notion-work-search.d.ts.map +1 -0
- package/dist/agent/channels/notion-work-search.js +69 -0
- package/dist/agent/channels/notion-work-search.js.map +1 -0
- package/dist/agent/channels/notion-work.d.ts +68 -0
- package/dist/agent/channels/notion-work.d.ts.map +1 -0
- package/dist/agent/channels/notion-work.js +126 -0
- package/dist/agent/channels/notion-work.js.map +1 -0
- package/dist/agent/channels/notion.d.ts +95 -0
- package/dist/agent/channels/notion.d.ts.map +1 -0
- package/dist/agent/channels/notion.js +1081 -0
- package/dist/agent/channels/notion.js.map +1 -0
- package/dist/agent/contributions.d.ts +32 -0
- package/dist/agent/contributions.d.ts.map +1 -0
- package/dist/agent/contributions.js +37 -0
- package/dist/agent/contributions.js.map +1 -0
- package/dist/agent/docs-generated.d.ts +3 -0
- package/dist/agent/docs-generated.d.ts.map +1 -0
- package/dist/agent/docs-generated.js +13 -0
- package/dist/agent/docs-generated.js.map +1 -0
- package/dist/agent/memory/blocks.d.ts +58 -0
- package/dist/agent/memory/blocks.d.ts.map +1 -0
- package/dist/agent/memory/blocks.js +264 -0
- package/dist/agent/memory/blocks.js.map +1 -0
- package/dist/agent/memory/memory.d.ts +157 -0
- package/dist/agent/memory/memory.d.ts.map +1 -0
- package/dist/agent/memory/memory.js +471 -0
- package/dist/agent/memory/memory.js.map +1 -0
- package/dist/agent/provider-events.d.ts +3 -0
- package/dist/agent/provider-events.d.ts.map +1 -0
- package/dist/agent/provider-events.js +196 -0
- package/dist/agent/provider-events.js.map +1 -0
- package/dist/agent/register.d.ts +5 -0
- package/dist/agent/register.d.ts.map +1 -0
- package/dist/agent/register.js +128 -0
- package/dist/agent/register.js.map +1 -0
- package/dist/agent/schedules/notion-ingress.d.ts +6 -0
- package/dist/agent/schedules/notion-ingress.d.ts.map +1 -0
- package/dist/agent/schedules/notion-ingress.js +16 -0
- package/dist/agent/schedules/notion-ingress.js.map +1 -0
- package/dist/agent/surface-parity.d.ts +3 -0
- package/dist/agent/surface-parity.d.ts.map +1 -0
- package/dist/agent/surface-parity.js +99 -0
- package/dist/agent/surface-parity.js.map +1 -0
- package/dist/agent/tools/notion_search.d.ts +56 -0
- package/dist/agent/tools/notion_search.d.ts.map +1 -0
- package/dist/agent/tools/notion_search.js +100 -0
- package/dist/agent/tools/notion_search.js.map +1 -0
- package/dist/agent/tools/read_notion_comments.d.ts +57 -0
- package/dist/agent/tools/read_notion_comments.d.ts.map +1 -0
- package/dist/agent/tools/read_notion_comments.js +236 -0
- package/dist/agent/tools/read_notion_comments.js.map +1 -0
- package/dist/agent/tools/read_notion_page.d.ts +53 -0
- package/dist/agent/tools/read_notion_page.d.ts.map +1 -0
- package/dist/agent/tools/read_notion_page.js +327 -0
- package/dist/agent/tools/read_notion_page.js.map +1 -0
- package/package.json +231 -0
- package/tsconfig.build.json +22 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// notion-webhook-timing — WHERE A NOTION WEBHOOK ACK'S SECONDS WENT (OPE-1929).
|
|
2
|
+
//
|
|
3
|
+
// Notion's comment route deliberately acknowledges only after the wake has been durably accepted.
|
|
4
|
+
// That makes every REST/store/queue wait on the path part of the webhook's observed latency. Keep
|
|
5
|
+
// this collector pure and injectable: production records one content-free sample, while a fake
|
|
6
|
+
// clock can prove the attribution without sleeping or calling a service.
|
|
7
|
+
export const NOTION_WEBHOOK_TIMING_ACTION = "notion.webhook_timing";
|
|
8
|
+
/**
|
|
9
|
+
* Run one whole route request and emit exactly one final sample. The caller decides when a normal
|
|
10
|
+
* outcome is final; any throw before that point becomes `threw` and is rethrown unchanged. Emission
|
|
11
|
+
* itself is telemetry and therefore fail-soft — it can never replace the HTTP result or error.
|
|
12
|
+
*/
|
|
13
|
+
export async function runNotionWebhookTimed(timing, emit, operation) {
|
|
14
|
+
let emitted = false;
|
|
15
|
+
const finish = (outcome) => {
|
|
16
|
+
if (emitted)
|
|
17
|
+
return;
|
|
18
|
+
emitted = true;
|
|
19
|
+
try {
|
|
20
|
+
emit(timing.detail(outcome));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Timing telemetry never changes the route's own result.
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
return await operation(finish);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
finish("threw");
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function duration(start, end) {
|
|
35
|
+
const elapsed = end - start;
|
|
36
|
+
return Number.isFinite(elapsed) && elapsed > 0 ? elapsed : 0;
|
|
37
|
+
}
|
|
38
|
+
const millis = (elapsed) => Math.round(elapsed);
|
|
39
|
+
/**
|
|
40
|
+
* Create one request-scoped recorder. Phases accumulate because some operations in a phase are
|
|
41
|
+
* separated by gates. `dispatch` deliberately contains `eveSend` and `finalAudit`; detail() removes
|
|
42
|
+
* those nested spans so the published buckets are mutually exclusive rather than double-counted.
|
|
43
|
+
*/
|
|
44
|
+
export function createNotionWebhookTiming(now = () => performance.now()) {
|
|
45
|
+
const startedAt = now();
|
|
46
|
+
const elapsed = new Map();
|
|
47
|
+
let failedPhase;
|
|
48
|
+
const add = (phase, start) => {
|
|
49
|
+
elapsed.set(phase, (elapsed.get(phase) ?? 0) + duration(start, now()));
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
async measure(phase, operation) {
|
|
53
|
+
const start = now();
|
|
54
|
+
try {
|
|
55
|
+
return await operation();
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
failedPhase ??= phase;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
add(phase, start);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
measureSync(phase, operation) {
|
|
66
|
+
const start = now();
|
|
67
|
+
try {
|
|
68
|
+
return operation();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
failedPhase ??= phase;
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
add(phase, start);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
detail(outcome) {
|
|
79
|
+
const eveSendMs = elapsed.get("eveSend");
|
|
80
|
+
const finalAuditMs = elapsed.get("finalAudit");
|
|
81
|
+
const dispatchMs = elapsed.get("dispatch");
|
|
82
|
+
return {
|
|
83
|
+
outcome,
|
|
84
|
+
...(failedPhase ? { failedPhase } : {}),
|
|
85
|
+
...(elapsed.has("credential") ? { hmacCredentialMs: millis(elapsed.get("credential")) } : {}),
|
|
86
|
+
...(elapsed.has("botTriggerPage") ? { botTriggerPageMs: millis(elapsed.get("botTriggerPage")) } : {}),
|
|
87
|
+
...(elapsed.has("ancestorSiblingTrust") ? { ancestorSiblingTrustMs: millis(elapsed.get("ancestorSiblingTrust")) } : {}),
|
|
88
|
+
...(elapsed.has("engagementDedupe") ? { engagementStoreDedupeMs: millis(elapsed.get("engagementDedupe")) } : {}),
|
|
89
|
+
...(dispatchMs === undefined
|
|
90
|
+
? {}
|
|
91
|
+
: { dispatchPreflightCatchUpMs: millis(Math.max(0, dispatchMs - (eveSendMs ?? 0) - (finalAuditMs ?? 0))) }),
|
|
92
|
+
...(eveSendMs === undefined ? {} : { eveSendMs: millis(eveSendMs) }),
|
|
93
|
+
...(finalAuditMs === undefined ? {} : { finalAuditMs: millis(finalAuditMs) }),
|
|
94
|
+
totalMs: millis(duration(startedAt, now())),
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=notion-webhook-timing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-webhook-timing.js","sourceRoot":"","sources":["../../../agent/channels/notion-webhook-timing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { WebhookVerifier } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
/** The registry key — what a generic hook declares as its `source` to be verified as Notion. */
|
|
3
|
+
export declare const NOTION_WEBHOOK_SOURCE = "notion";
|
|
4
|
+
export declare const notionWebhookVerifier: WebhookVerifier;
|
|
5
|
+
//# sourceMappingURL=notion-webhook-verifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-webhook-verifier.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-webhook-verifier.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAElF,gGAAgG;AAChG,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAI9C,eAAO,MAAM,qBAAqB,EAAE,eAyBnC,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Notion's webhook signature scheme, as a registry entry (OPE-164) — what lets a GENERIC hook
|
|
2
|
+
// (`POST /webhooks/<id>`) declare `source: "notion"` and be verified the way Notion signs.
|
|
3
|
+
//
|
|
4
|
+
// ZERO NEW CRYPTO. The digest is this bundle's own `verifyNotionSignature` (channels/notion-api.ts),
|
|
5
|
+
// the same function the native notion webhook route uses — one implementation of Notion's scheme in
|
|
6
|
+
// the tree, not two.
|
|
7
|
+
//
|
|
8
|
+
// THE SCHEME (https://developers.notion.com/reference/webhooks):
|
|
9
|
+
// X-Notion-Signature: sha256=<hex> HMAC-SHA256 of the RAW body, keyed by the ONE-TIME
|
|
10
|
+
// `verification_token` Notion hands over during subscription setup.
|
|
11
|
+
// Notion, verbatim, on why the raw bytes matter: *"Pass the raw request body exactly as it arrived
|
|
12
|
+
// (re-serialized JSON produces different bytes and fails verification)"*.
|
|
13
|
+
//
|
|
14
|
+
// 🔴 `signedTimestamp: false` — nothing in the signature binds freshness. Notion's payloads carry a
|
|
15
|
+
// timestamp FIELD, but it is not covered in a way that bounds replay, so replay against a notion
|
|
16
|
+
// hook is undefended and is declared as such on the reviewed allowlist.
|
|
17
|
+
//
|
|
18
|
+
// ROTATION: Notion documents no overlap window — *"If you ever rotate or recreate your webhook
|
|
19
|
+
// subscription, be sure to update your stored verification_token"* — so the `keys[]` list is how an
|
|
20
|
+
// operator gets one on our side (new token at index 0, outgoing at index 1 until the switch).
|
|
21
|
+
import { verifyNotionSignature } from "./notion-api.js";
|
|
22
|
+
/** The registry key — what a generic hook declares as its `source` to be verified as Notion. */
|
|
23
|
+
export const NOTION_WEBHOOK_SOURCE = "notion";
|
|
24
|
+
const NOTION_SIGNATURE_HEADER = "x-notion-signature";
|
|
25
|
+
export const notionWebhookVerifier = {
|
|
26
|
+
source: NOTION_WEBHOOK_SOURCE,
|
|
27
|
+
provider: "notion",
|
|
28
|
+
keyKind: "shared-secret",
|
|
29
|
+
// 🔴 `keyOrigin: "vendor-issued"` (OPE-186) — the key is the `verification_token` NOTION mints and
|
|
30
|
+
// posts to us once during subscription setup. We cannot choose it, so a secret WE minted can never
|
|
31
|
+
// verify a Notion delivery. This is what keeps `notion` OUT of the chat-mintable source vocabulary
|
|
32
|
+
// (`webhooks/source-vocabulary.ts` → `acceptsMintedSecret`). 🔴 THAT CONDITIONAL IS NO LONGER
|
|
33
|
+
// HYPOTHETICAL: OPE-189 added `notion-event-normalizer.ts`, so `notion` now has BOTH halves the
|
|
34
|
+
// vocabulary derivation looks for and `keyOrigin` is the ONLY thing excluding it. The exclusion is
|
|
35
|
+
// pinned live rather than asserted here — `scripts/webhook-source-vocabulary-live-test.ts` §6a
|
|
36
|
+
// fails any mintable source whose normalizer declares no `deliveryIdHeader`, and Notion's declares
|
|
37
|
+
// none (its delivery id is in the body). Flip this field and that test goes red. Only an
|
|
38
|
+
// OPERATOR-DECLARED hook (`loadWebhooks`, whose secret ref the operator points at the stored token)
|
|
39
|
+
// can use this verifier.
|
|
40
|
+
keyOrigin: "vendor-issued",
|
|
41
|
+
signedTimestamp: false,
|
|
42
|
+
verify({ rawBody, headers, keys }) {
|
|
43
|
+
const signature = headers.get(NOTION_SIGNATURE_HEADER);
|
|
44
|
+
if (!signature)
|
|
45
|
+
return { ok: false, failure: "missing-signature" };
|
|
46
|
+
for (const [i, key] of keys.entries()) {
|
|
47
|
+
if (verifyNotionSignature(key, rawBody, signature))
|
|
48
|
+
return { ok: true, keyIndex: i };
|
|
49
|
+
}
|
|
50
|
+
return { ok: false, failure: "signature-mismatch" };
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=notion-webhook-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-webhook-verifier.js","sourceRoot":"","sources":["../../../agent/channels/notion-webhook-verifier.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,2FAA2F;AAC3F,EAAE;AACF,qGAAqG;AACrG,oGAAoG;AACpG,qBAAqB;AACrB,EAAE;AACF,iEAAiE;AACjE,0FAA0F;AAC1F,sEAAsE;AACtE,mGAAmG;AACnG,0EAA0E;AAC1E,EAAE;AACF,oGAAoG;AACpG,iGAAiG;AACjG,wEAAwE;AACxE,EAAE;AACF,+FAA+F;AAC/F,oGAAoG;AACpG,8FAA8F;AAE9F,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,gGAAgG;AAChG,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AAE9C,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAErD,MAAM,CAAC,MAAM,qBAAqB,GAAoB;IACpD,MAAM,EAAE,qBAAqB;IAC7B,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,eAAe;IACxB,mGAAmG;IACnG,mGAAmG;IACnG,mGAAmG;IACnG,8FAA8F;IAC9F,gGAAgG;IAChG,mGAAmG;IACnG,+FAA+F;IAC/F,mGAAmG;IACnG,yFAAyF;IACzF,oGAAoG;IACpG,yBAAyB;IACzB,SAAS,EAAE,eAAe;IAC1B,eAAe,EAAE,KAAK;IACtB,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;QAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACvD,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;QACnE,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACtC,IAAI,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QACvF,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;IACtD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { type FetchLike, type NotionComment, type NotionSigningKey, type NotionCallOpts, type ResolvedNotionCredential } from "./notion-api.js";
|
|
2
|
+
import { type NotionTrustRoots } from "./notion-ancestry-trust.js";
|
|
3
|
+
import { type NotionUntieredSupervisorDispatch } from "./notion-supervisor.js";
|
|
4
|
+
import { type NotionEngagementDeps } from "./notion-engagement.js";
|
|
5
|
+
import type { NotionWebhookTimingRecorder } from "./notion-webhook-timing.js";
|
|
6
|
+
import type { NotionCommentDispatchGuard } from "./notion-ingress-drain.js";
|
|
7
|
+
import { type AttachSessionForCancel, type Domain } from "@open-everywhere/core/agent/adapter-api.js";
|
|
8
|
+
/** The per-TURN reply target the channel snapshots at `turn.started` (see notion.ts). */
|
|
9
|
+
export interface NotionReplyTarget {
|
|
10
|
+
readonly discussionId: string;
|
|
11
|
+
readonly pageId?: string;
|
|
12
|
+
readonly workspaceId?: string;
|
|
13
|
+
/** The Notion integration THIS delivery verified under (OPE-467) — snapshotted with the rest of
|
|
14
|
+
* the target so the terminal reply posts under the credential that fetched the comment. Absent
|
|
15
|
+
* on a turn whose auth predates the stamp ⇒ the post falls back to the page's domain binding. */
|
|
16
|
+
readonly credentialId?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The per-session state the channel stashes so `message.completed` can post the reply into
|
|
19
|
+
* the SAME discussion thread (see notion.ts's context factory). */
|
|
20
|
+
export interface NotionSessionState {
|
|
21
|
+
readonly discussionId: string | null;
|
|
22
|
+
readonly pageId: string | null;
|
|
23
|
+
readonly workspaceId: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* The PER-TURN reply target, snapshotted at `turn.started` from the delivery's auth
|
|
26
|
+
* attributes — NOT the seeded fields above. eve applies a `send({ state })` seed only when
|
|
27
|
+
* it CREATES a session; a resumed (standing supervisor) session keeps its original state, so
|
|
28
|
+
* the seeded `discussionId` goes stale after the first wake and every later reply would land
|
|
29
|
+
* in the FIRST discussion. eve rewrites `session.auth.current` from the delivery's auth on
|
|
30
|
+
* every delivery, so the auth attributes ARE this turn's real trigger — the same snapshot
|
|
31
|
+
* pattern slack.ts's supervisor turn.started uses (T1.4). Mutable by design (assigned per
|
|
32
|
+
* turn); `undefined` = no resolvable target, and message.completed FAILS SAFE (posts nothing).
|
|
33
|
+
*/
|
|
34
|
+
oeReplyTarget?: NotionReplyTarget;
|
|
35
|
+
/**
|
|
36
|
+
* True iff this session was CREATED as a UNIFIED (org, domain) supervisor session
|
|
37
|
+
* (notion-supervisor.ts seeds it; eve applies the seed at creation only, and a session's grain
|
|
38
|
+
* is fixed for its lifetime — the token shape can't change mid-generation). notion.ts's
|
|
39
|
+
* message.completed keys the message-tool-only silence gate on it — Notion has a WORKING
|
|
40
|
+
* `reply` tool on supervisor turns, so a unified turn's terminal prose is DROPPED (never
|
|
41
|
+
* posted) and a stray leak is audited. Absent/false on every per-doc session ⇒ the gate never
|
|
42
|
+
* fires ⇒ byte-identical flag-off behaviour.
|
|
43
|
+
*/
|
|
44
|
+
readonly oeUnified?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface HandleNotionDeliveryDeps {
|
|
47
|
+
/**
|
|
48
|
+
* EVERY configured Notion integration's inbound signing key, org default first
|
|
49
|
+
* (channels/notion-credentials.ts notionIntegrations). The gate tries each in turn and the one
|
|
50
|
+
* that verifies IDENTIFIES the sending integration — see gateNotionDeliveryAcross. A single-
|
|
51
|
+
* integration deployment passes a one-entry list and behaves exactly as it did before OPE-467.
|
|
52
|
+
*/
|
|
53
|
+
readonly signingKeys: readonly NotionSigningKey[];
|
|
54
|
+
/** The `webhooks.allowUnverifiedNotion` setting — dev escape hatch only. */
|
|
55
|
+
readonly allowUnverified: boolean;
|
|
56
|
+
/** Resolve one Notion channel credential to its integration token AND that credential's endpoint
|
|
57
|
+
* rules (resolveNotionCredential). Called with the credential id the VERIFYING integration named
|
|
58
|
+
* (else {@link credentialForPage}), so a delivery's REST calls run under the integration that
|
|
59
|
+
* actually sent it. Undefined ⇒ unconfigured, and the delivery is acked without a dispatch (never
|
|
60
|
+
* a retry storm over a config bug).
|
|
61
|
+
*
|
|
62
|
+
* 🔴 The PAIR, never the bare key (OPE-505). Every REST call in this file spreads the result, so
|
|
63
|
+
* the credential's endpoint fence rides along; handing on only the token would return an unfenced
|
|
64
|
+
* fetch and silently disable the check on the whole webhook two-step. */
|
|
65
|
+
readonly credentialFor: (credentialId: string) => Promise<ResolvedNotionCredential | undefined>;
|
|
66
|
+
/** The credential the PAGE'S DOMAIN binds, or undefined when it binds none — see
|
|
67
|
+
* channels/notion-credentials.ts. It decides the credential for READS and REPLIES the agent
|
|
68
|
+
* initiates. On an INBOUND delivery it is only the fallback: the HMAC has already proven which
|
|
69
|
+
* integration sent this body, and a page id read off an unauthenticated payload never overrides
|
|
70
|
+
* a proof (step 5). */
|
|
71
|
+
readonly credentialForPage: (workspaceId: string | undefined, pageId: string | undefined) => string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* The domain registry, passed as DATA rather than as a resolved verdict (OPE-502).
|
|
74
|
+
*
|
|
75
|
+
* 🔴 THE FENCE ITSELF STAYS INSIDE THIS CORE ON PURPOSE. Step 8b's decision is a security
|
|
76
|
+
* boundary, so it must not be a closure a caller supplies — a caller that passed a permissive one,
|
|
77
|
+
* or forgot the field, would reopen the whole vector with nothing to notice it. `domains` is inert
|
|
78
|
+
* config, so the only thing a caller can get wrong is passing an EMPTY list, and an empty list
|
|
79
|
+
* fences nothing because it owns nothing. Required, never optional, for the same reason.
|
|
80
|
+
*
|
|
81
|
+
* The route hands it `getConfig().loadDomains()`, fail-soft to `[]` — the same value it already
|
|
82
|
+
* closes over for {@link credentialForPage} and {@link ambientEnabled}.
|
|
83
|
+
*/
|
|
84
|
+
readonly domains: readonly Domain[];
|
|
85
|
+
/**
|
|
86
|
+
* The operator's declared Notion TRUST ROOTS, prepared (OPE-492) — the route passes
|
|
87
|
+
* `notionTrustRoots()`, the register core's `subtree: true` trust entries fill at boot (OPE-539).
|
|
88
|
+
*
|
|
89
|
+
* OPTIONAL, and that is safe where {@link HandleNotionDeliveryDeps.domains} is not: a missing
|
|
90
|
+
* roots list can only ever LOSE a promotion. Absent (or empty) ⇒ every page resolves `no-roots`,
|
|
91
|
+
* with no API call and no probe, and the delivery keeps the fail-closed `external` binding it had
|
|
92
|
+
* before this seam existed. `domains` is required for the opposite reason — a missing one would
|
|
93
|
+
* weaken a fence.
|
|
94
|
+
*/
|
|
95
|
+
readonly trustRoots?: NotionTrustRoots;
|
|
96
|
+
/** Resolve the bot's own Notion user id FOR ONE INTEGRATION (cached per credential; env override
|
|
97
|
+
* or GET /v1/users/me). An unresolved id makes classifyNotionComment fail CLOSED (drop, never
|
|
98
|
+
* wake) — the loop guard. 🔴 It is per-integration on purpose: each Notion integration has its
|
|
99
|
+
* OWN bot user id, so one cached id shared across integrations would compare a reply against the
|
|
100
|
+
* wrong identity and break the loop guard in both directions.
|
|
101
|
+
* 🔴 THE CORE CALLS IT ONCE PER CONFIGURED INTEGRATION, not once per delivery (OPE-1921): step 7
|
|
102
|
+
* walks {@link signingKeys} to build the deployment's whole own-id set. There is deliberately no
|
|
103
|
+
* separate "all the ids" dep — the set is DERIVED here from a required one, so no caller can
|
|
104
|
+
* omit it and silently reopen the sibling loop, exactly as {@link domains} is required. */
|
|
105
|
+
readonly resolveBotUserId: (credentialId: string) => Promise<string | undefined>;
|
|
106
|
+
/** Resolve the bot's own display name for one integration (Step 4) — a judge input (the
|
|
107
|
+
* channel-watch preamble's "names you"), never a bypass. Optional so the core works before name
|
|
108
|
+
* capture is wired. */
|
|
109
|
+
readonly resolveBotName?: (credentialId: string) => Promise<string | undefined>;
|
|
110
|
+
/** Is the ambient supervisor enabled for this doc's surfaceKey? Fail-closed OFF (per-domain
|
|
111
|
+
* opt-in, ambientSupervisorEnabled). The WHOLE ambient path is gated behind this. */
|
|
112
|
+
readonly ambientEnabled: (surfaceKey: string) => boolean;
|
|
113
|
+
/** Resolve the doc's engagement calibration tier (memory/domain.ts engagementTierFor) for the
|
|
114
|
+
* judged preamble. Optional + fail-soft: absent/throwing ⇒ neutral (byte-identical). */
|
|
115
|
+
readonly engagementTier?: (surfaceKey: string) => "aggressive" | "neutral" | "reserved";
|
|
116
|
+
/** The §2.5 engagement seam's store seams (OPE-1501) — see channels/notion-engagement.ts.
|
|
117
|
+
* Optional: absent ⇒ the live operational store. Injected so a test drives an engaged or a
|
|
118
|
+
* stood-down page without one. */
|
|
119
|
+
readonly engagement?: NotionEngagementDeps;
|
|
120
|
+
/** Durable comment-level dispatch lease used only by the authenticated inbox processor. The
|
|
121
|
+
* synchronous development path keeps the legacy event_dedupe guard. */
|
|
122
|
+
readonly dispatchGuard?: NotionCommentDispatchGuard;
|
|
123
|
+
/** Injectable fetch (tests pass a mock; prod omits → global fetch). */
|
|
124
|
+
readonly fetch?: FetchLike;
|
|
125
|
+
readonly version?: string;
|
|
126
|
+
/** OVERLAY on the 429 policy every REST call of this delivery takes (OPE-1254). Live code passes
|
|
127
|
+
* none and gets {@link NOTION_INGRESS_RETRY} whole — the policy sized against the ingress
|
|
128
|
+
* function's 300 s ceiling. A test passes `{ sleep }` alone, which keeps the shipped numbers and
|
|
129
|
+
* only makes the waits instant. */
|
|
130
|
+
readonly retry?: NotionCallOpts["retry"];
|
|
131
|
+
/** Request-scoped, content-free latency attribution. Absent keeps non-route callers unchanged. */
|
|
132
|
+
readonly timing?: NotionWebhookTimingRecorder;
|
|
133
|
+
/** Route the classified comment to the standing doc supervisor (notion-supervisor.ts). The
|
|
134
|
+
* dispatch it receives is UNTIERED (OPE-596): this core holds no binding context, so the route
|
|
135
|
+
* leg resolves each author's tier before the supervisor core renders anything. */
|
|
136
|
+
readonly dispatch: (d: NotionUntieredSupervisorDispatch) => Promise<void>;
|
|
137
|
+
/** Called with the one-time verification token on the handshake POST (channel logs it). */
|
|
138
|
+
readonly onHandshakeToken?: (token: string) => void;
|
|
139
|
+
/** eve's cancel door, channel-agnostic (the route's own `args.attachSession`) — what the STOP LEG
|
|
140
|
+
* (OPE-989, step 8d below) calls through {@link routeNotionStopComment}. */
|
|
141
|
+
readonly attachSession: AttachSessionForCancel;
|
|
142
|
+
}
|
|
143
|
+
export interface NotionDeliveryOutcome {
|
|
144
|
+
readonly status: number;
|
|
145
|
+
readonly body: string;
|
|
146
|
+
readonly action: "handshake" | "rejected" | "bad-json" | "ignored" | "ambient-disabled" | "skipped-self" | "skipped-bot" | "dropped" | "dispatched" | "no-api-key" | "unresolved-page"
|
|
147
|
+
/** A bare `stop` comment from a genuine, distinct human (OPE-989) — the turn on this page was
|
|
148
|
+
* handed to `cancelAnchoredTurn`. `detail` carries the cancel outcome, never a refusal reason. */
|
|
149
|
+
| "stopped"
|
|
150
|
+
/** A plain comment on a page `stand_down` marked (OPE-1501) — suppressed by the §2.5 engagement
|
|
151
|
+
* registry, whatever the ambient opt-in says. `detail` carries the engagement anchor. Naming
|
|
152
|
+
* the agent again clears the flag, so this is never the outcome of an addressed comment. */
|
|
153
|
+
| "stood-down"
|
|
154
|
+
/** The signer does not serve the domain that owns this delivery's page (OPE-502, step 8b). */
|
|
155
|
+
| "scope-refused"
|
|
156
|
+
/** This comment id already woke a turn inside the dedupe window (OPE-1906, step 12) — a vendor
|
|
157
|
+
* retry, or a page two subscriptions both deliver. `detail` carries the comment id. Acked 200:
|
|
158
|
+
* the delivery was handled the first time, so Notion has nothing to retry. */
|
|
159
|
+
| "duplicate-delivery" | "error";
|
|
160
|
+
readonly detail?: string;
|
|
161
|
+
/**
|
|
162
|
+
* 🔴 A REAL `x-notion-signature` WAS CHECKED AGAINST A CONFIGURED SECRET (OPE-189).
|
|
163
|
+
*
|
|
164
|
+
* NOT the same as "the gate let it through". `gateNotionDelivery` also returns "accepted" when no
|
|
165
|
+
* secret is set AND `webhooks.allowUnverifiedNotion` is on — a local-dev hatch whose only proof is
|
|
166
|
+
* possession of the URL. This flag is false in that case, and the route uses it to decide whether
|
|
167
|
+
* the delivery may enter the ROUTING plane at all.
|
|
168
|
+
*
|
|
169
|
+
* Why the two consumers differ, deliberately: the ambient supervisor reads a comment and judges
|
|
170
|
+
* whether to reply, which is what the hatch exists to let you exercise on a laptop. `admitTrigger`
|
|
171
|
+
* fires member-authored, human-approved routines — so an unsigned acceptance must not reach it, or
|
|
172
|
+
* a dev hatch becomes a production capability the moment the env var is set on a real deployment.
|
|
173
|
+
* Same narrowing S0b applied to runtime hooks, for the same reason.
|
|
174
|
+
*
|
|
175
|
+
* False for the handshake (it arrives BEFORE any secret exists and is correctly unverified) and
|
|
176
|
+
* for every rejection.
|
|
177
|
+
*/
|
|
178
|
+
readonly verified: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* The Notion channel credential this delivery ran under (OPE-467) — the integration whose secret
|
|
181
|
+
* verified the signature, else (dev hatch only) the page's domain binding. Absent when the
|
|
182
|
+
* delivery never reached credential selection (handshake, rejection, bad json, ignored event).
|
|
183
|
+
* Reported so the route can audit WHICH integration served a page without a second resolution.
|
|
184
|
+
*/
|
|
185
|
+
readonly credentialId?: string;
|
|
186
|
+
/**
|
|
187
|
+
* 🔴 THE INTEGRATION WHOSE SECRET VERIFIED THIS BODY (OPE-500) — the ONE authenticated identity a
|
|
188
|
+
* delivery carries, and a strict subset of {@link NotionDeliveryOutcome.credentialId}.
|
|
189
|
+
*
|
|
190
|
+
* NOT the same field. `credentialId` is "which integration SERVED this delivery", which falls back
|
|
191
|
+
* to the page binding and then to the first configured key on the dev-hatch arm — so it can name an
|
|
192
|
+
* integration that proved nothing. This one is set at exactly one place, from the HMAC verdict, and
|
|
193
|
+
* is absent whenever no secret verified (handshake, rejection, the unverified dev hatch).
|
|
194
|
+
*
|
|
195
|
+
* Present on EVERY arm past the gate, `ignored` included. The event plane needs it precisely on the
|
|
196
|
+
* arm the comment path drops: a `page.*` delivery never reaches credential selection, and
|
|
197
|
+
* `notionTriggerScopeVerdict` cannot fence a scope without knowing who signed for it.
|
|
198
|
+
*/
|
|
199
|
+
readonly signerCredentialId?: string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Run the whole inbound loop for one Notion webhook delivery. Returns the HTTP outcome the
|
|
203
|
+
* route should send AND the semantic action (for the audit row). Pure w.r.t. eve — every
|
|
204
|
+
* external effect is injected (fetch, resolveBotUserId, resolveBotName, ambientEnabled, dispatch,
|
|
205
|
+
* onHandshakeToken).
|
|
206
|
+
*/
|
|
207
|
+
export declare function handleNotionDelivery(rawBody: string, signatureHeader: string | null, deps: HandleNotionDeliveryDeps): Promise<NotionDeliveryOutcome>;
|
|
208
|
+
/**
|
|
209
|
+
* Process a row that crossed {@link handleNotionDelivery}'s HMAC gate before it was committed to
|
|
210
|
+
* the durable ingress inbox. This is intentionally a separate entrypoint: the public route cannot
|
|
211
|
+
* pass an optional "already verified" flag, and the internal drain must supply the immutable signer
|
|
212
|
+
* identity from the row key. A deleted integration is not retargeted to another credential.
|
|
213
|
+
*/
|
|
214
|
+
export declare function handleAcceptedNotionDelivery(rawBody: string, signerCredentialId: string, deps: HandleNotionDeliveryDeps): Promise<NotionDeliveryOutcome>;
|
|
215
|
+
/** {@link resolveDeliveryPage}'s answer. Exactly one field is set: `pageId` when the page is
|
|
216
|
+
* established, `reason` when it is not. A `reason` is a REFUSAL — never a page id the caller may
|
|
217
|
+
* substitute. */
|
|
218
|
+
export interface ResolvedDeliveryPage {
|
|
219
|
+
readonly pageId?: string;
|
|
220
|
+
readonly reason?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The page a delivery's comment REALLY lives on, established from the comment Notion returned under
|
|
224
|
+
* the verified credential (OPE-496). The webhook's step 8 explains why the payload's own `page_id`
|
|
225
|
+
* may not answer this; this function is the answer that replaces it.
|
|
226
|
+
*
|
|
227
|
+
* Two shapes, one credential:
|
|
228
|
+
* • a PAGE-level comment carries `parent.page_id` — already the answer, and already authenticated
|
|
229
|
+
* (the fetch that returned it ran under the integration whose secret verified the delivery);
|
|
230
|
+
* • an INLINE comment carries `parent.block_id` instead, so the block's owning page is walked under
|
|
231
|
+
* the SAME credential ({@link resolveBlockOwningPage}, bounded at BLOCK_PARENT_MAX_HOPS).
|
|
232
|
+
*
|
|
233
|
+
* Every other outcome is a refusal with a reason: no parent at all, a parent that is neither a page
|
|
234
|
+
* nor a block, or a block whose walk ends anywhere but a page (a database or workspace parent, an id
|
|
235
|
+
* the integration cannot see, or too many hops). The caller must treat a reason as "do nothing" —
|
|
236
|
+
* `resolveBlockOwningPage` already states the same rule for the read fence, and both callers keep it.
|
|
237
|
+
*
|
|
238
|
+
* Exported so the delivery test can drive each shape directly, with the mock fetch counting the
|
|
239
|
+
* block-walk calls.
|
|
240
|
+
*/
|
|
241
|
+
export declare function resolveDeliveryPage(comment: NotionComment, opts: NotionCallOpts): Promise<ResolvedDeliveryPage>;
|
|
242
|
+
//# sourceMappingURL=notion-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-webhook.d.ts","sourceRoot":"","sources":["../../../agent/channels/notion-webhook.ts"],"names":[],"mappings":"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"}
|