@nodii/telemetry 0.10.0 → 0.11.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/dist/context-adapter/claims.d.ts +89 -0
- package/dist/context-adapter/claims.d.ts.map +1 -0
- package/dist/context-adapter/claims.js +113 -0
- package/dist/context-adapter/claims.js.map +1 -0
- package/dist/context-adapter/grpc.d.ts +62 -0
- package/dist/context-adapter/grpc.d.ts.map +1 -0
- package/dist/context-adapter/grpc.js +113 -0
- package/dist/context-adapter/grpc.js.map +1 -0
- package/dist/context-adapter/hono.d.ts +39 -0
- package/dist/context-adapter/hono.d.ts.map +1 -0
- package/dist/context-adapter/hono.js +86 -0
- package/dist/context-adapter/hono.js.map +1 -0
- package/dist/context-adapter/index.d.ts +4 -0
- package/dist/context-adapter/index.d.ts.map +1 -0
- package/dist/context-adapter/index.js +17 -0
- package/dist/context-adapter/index.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/outbox/index.d.ts +2 -0
- package/dist/outbox/index.d.ts.map +1 -0
- package/dist/outbox/index.js +13 -0
- package/dist/outbox/index.js.map +1 -0
- package/package.json +17 -4
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ActorType, NodiiContext } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The minimal verified-claims surface the adapters consume. This is a
|
|
4
|
+
* STRUCTURAL contract — both `@nodii/auth-sdk`'s `NodiiClaims`
|
|
5
|
+
* (`sub`/`principalKind`/`roles`/`actor`) and the gRPC
|
|
6
|
+
* `UserActorEnvelope` map onto it. Every field is optional so the
|
|
7
|
+
* adapter degrades gracefully when a claim is absent (e.g. an S2S-only
|
|
8
|
+
* call with no user actor).
|
|
9
|
+
*/
|
|
10
|
+
export interface AdapterClaims {
|
|
11
|
+
/** Tenant the principal is acting within. NodiiContext.tenant_id. */
|
|
12
|
+
tenantId?: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Principal id. Becomes NodiiContext.user_id ONLY when the resolved
|
|
15
|
+
* actor_type is 'user' (the type invariant: user_id is null otherwise).
|
|
16
|
+
*/
|
|
17
|
+
subjectId?: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Subject kind from the token. Maps to actor_type. Accepts the
|
|
20
|
+
* auth-sdk `principalKind` vocabulary ('user' | 'customer' | 'agent' |
|
|
21
|
+
* 'service') plus 'system'; anything unrecognized falls back to
|
|
22
|
+
* 'service' (the safe non-user, non-agent default).
|
|
23
|
+
*/
|
|
24
|
+
subjectKind?: string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Tenant-defined role names the principal holds. Collapsed to a single
|
|
27
|
+
* NodiiContext.role via highest-privilege selection.
|
|
28
|
+
*/
|
|
29
|
+
roles?: readonly string[] | null;
|
|
30
|
+
/**
|
|
31
|
+
* Delegation / on-behalf-of claim. The id of the principal this
|
|
32
|
+
* actor is acting on behalf of (act/obo). Maps to on_behalf_of.
|
|
33
|
+
*/
|
|
34
|
+
onBehalfOf?: string | null;
|
|
35
|
+
/** Intent id if one is already active on the inbound call. */
|
|
36
|
+
intentId?: string | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Default role-privilege ordering, highest first. Used to collapse a
|
|
40
|
+
* `roles[]` claim down to the single `NodiiContext.role`. There is no
|
|
41
|
+
* canonical RBAC role ordering elsewhere in nodii-libs (the DB `nodii_*`
|
|
42
|
+
* roles in db-rls are Postgres roles, not RBAC role names), and the
|
|
43
|
+
* UserActorEnvelope doc cites "owner" > "admin" > "member" as the
|
|
44
|
+
* representative ladder — this extends that ladder with the common
|
|
45
|
+
* management / read-only tiers. Callers with a service-specific ladder
|
|
46
|
+
* pass `rolePrivilegeOrder` to override it.
|
|
47
|
+
*
|
|
48
|
+
* Selection rule: the role appearing EARLIEST in this list wins. A role
|
|
49
|
+
* not in the list ranks BELOW every listed role (least privilege), so an
|
|
50
|
+
* unknown custom role never silently outranks a known privileged one.
|
|
51
|
+
*/
|
|
52
|
+
export declare const ROLE_PRIVILEGE_ORDER: readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* Collapse a `roles[]` claim to the single highest-privilege role.
|
|
55
|
+
* Returns `fallback` ('member' by default) when the array is empty/absent.
|
|
56
|
+
*/
|
|
57
|
+
export declare function collapseRolesToHighest(roles: readonly string[] | null | undefined, order?: readonly string[], fallback?: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Map a token subject kind to the locked `ActorType` enum. `customer`
|
|
60
|
+
* (an auth-sdk principalKind) is a user-facing principal → 'user'.
|
|
61
|
+
* Unrecognized kinds fall back to 'service' (safe non-user default).
|
|
62
|
+
*/
|
|
63
|
+
export declare function deriveActorType(subjectKind?: string | null): ActorType;
|
|
64
|
+
export interface MapClaimsOptions {
|
|
65
|
+
/**
|
|
66
|
+
* The inbound `x-request-id` value (header or gRPC metadata). When
|
|
67
|
+
* absent/empty a fresh uuidv7 is minted — request_id is NEVER empty in
|
|
68
|
+
* a NodiiContext.
|
|
69
|
+
*/
|
|
70
|
+
requestId?: string | null;
|
|
71
|
+
/** Override the default role-privilege ladder. */
|
|
72
|
+
rolePrivilegeOrder?: readonly string[];
|
|
73
|
+
/** Override the fallback role when no roles claim is present. */
|
|
74
|
+
roleFallback?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Fallback tenant_id when the claims carry none. NodiiContext.tenant_id
|
|
77
|
+
* is non-nullable; if neither claim nor fallback supplies it the adapter
|
|
78
|
+
* throws (a context with no tenant is not safe to set — RLS keys off it).
|
|
79
|
+
*/
|
|
80
|
+
tenantFallback?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build a `NodiiContext` from verified claims + the inbound request_id.
|
|
84
|
+
* Pure + deterministic given a fixed request_id; the adapters call it
|
|
85
|
+
* after extracting the request_id (so the generated-uuid path is
|
|
86
|
+
* observable/testable).
|
|
87
|
+
*/
|
|
88
|
+
export declare function mapClaimsToContext(claims: AdapterClaims, opts?: MapClaimsOptions): NodiiContext;
|
|
89
|
+
//# sourceMappingURL=claims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../src/context-adapter/claims.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3D;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAOxC,CAAC;AAEX;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,EAC3C,KAAK,GAAE,SAAS,MAAM,EAAyB,EAC/C,QAAQ,SAAW,GAClB,MAAM,CAmBR;AASD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAQtE;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,IAAI,GAAE,gBAAqB,GAC1B,YAAY,CA+Bd"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Shared NodiiContext mapping for the entry-seam adapters (D44 § 6.5,
|
|
2
|
+
// D405 context adoption). The Hono middleware (`./hono.ts`) and the gRPC
|
|
3
|
+
// server interceptor (`./grpc.ts`) both funnel through `mapClaimsToContext`
|
|
4
|
+
// so the field mappings are defined exactly once.
|
|
5
|
+
//
|
|
6
|
+
// Operator-LOCKED field mappings (req b3b03fc5, clarification c9906a51,
|
|
7
|
+
// approved 2026-06-25):
|
|
8
|
+
// - actor_type — derived from the token's subject kind.
|
|
9
|
+
// - roles[] → role — NodiiContext.role is singular; collapse roles[] to
|
|
10
|
+
// the HIGHEST-privilege role (see ROLE_PRIVILEGE_ORDER).
|
|
11
|
+
// - on_behalf_of — sourced from the delegation (act/obo) claim.
|
|
12
|
+
// - request_id — read from x-request-id; GENERATED (uuidv7) if absent.
|
|
13
|
+
// tenant_id / intent_id / user_id come from the claims; trace activation
|
|
14
|
+
// is handled separately by the adapter (it activates the inbound
|
|
15
|
+
// trace_parent onto the OTel context, never folded into NodiiContext).
|
|
16
|
+
import { uuidv7 } from "../uuid.js";
|
|
17
|
+
/**
|
|
18
|
+
* Default role-privilege ordering, highest first. Used to collapse a
|
|
19
|
+
* `roles[]` claim down to the single `NodiiContext.role`. There is no
|
|
20
|
+
* canonical RBAC role ordering elsewhere in nodii-libs (the DB `nodii_*`
|
|
21
|
+
* roles in db-rls are Postgres roles, not RBAC role names), and the
|
|
22
|
+
* UserActorEnvelope doc cites "owner" > "admin" > "member" as the
|
|
23
|
+
* representative ladder — this extends that ladder with the common
|
|
24
|
+
* management / read-only tiers. Callers with a service-specific ladder
|
|
25
|
+
* pass `rolePrivilegeOrder` to override it.
|
|
26
|
+
*
|
|
27
|
+
* Selection rule: the role appearing EARLIEST in this list wins. A role
|
|
28
|
+
* not in the list ranks BELOW every listed role (least privilege), so an
|
|
29
|
+
* unknown custom role never silently outranks a known privileged one.
|
|
30
|
+
*/
|
|
31
|
+
export const ROLE_PRIVILEGE_ORDER = [
|
|
32
|
+
"owner",
|
|
33
|
+
"admin",
|
|
34
|
+
"manager",
|
|
35
|
+
"member",
|
|
36
|
+
"viewer",
|
|
37
|
+
"guest",
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Collapse a `roles[]` claim to the single highest-privilege role.
|
|
41
|
+
* Returns `fallback` ('member' by default) when the array is empty/absent.
|
|
42
|
+
*/
|
|
43
|
+
export function collapseRolesToHighest(roles, order = ROLE_PRIVILEGE_ORDER, fallback = "member") {
|
|
44
|
+
if (!roles || roles.length === 0)
|
|
45
|
+
return fallback;
|
|
46
|
+
const rank = (r) => {
|
|
47
|
+
const i = order.indexOf(r);
|
|
48
|
+
// Unknown roles rank below every known role (largest index).
|
|
49
|
+
return i === -1 ? order.length : i;
|
|
50
|
+
};
|
|
51
|
+
// Pick the role with the smallest rank index; ties broken by input order.
|
|
52
|
+
let best = roles[0];
|
|
53
|
+
let bestRank = rank(best);
|
|
54
|
+
for (let i = 1; i < roles.length; i++) {
|
|
55
|
+
const candidate = roles[i];
|
|
56
|
+
const candidateRank = rank(candidate);
|
|
57
|
+
if (candidateRank < bestRank) {
|
|
58
|
+
best = candidate;
|
|
59
|
+
bestRank = candidateRank;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
const ACTOR_TYPES = [
|
|
65
|
+
"user",
|
|
66
|
+
"agent",
|
|
67
|
+
"system",
|
|
68
|
+
"service",
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* Map a token subject kind to the locked `ActorType` enum. `customer`
|
|
72
|
+
* (an auth-sdk principalKind) is a user-facing principal → 'user'.
|
|
73
|
+
* Unrecognized kinds fall back to 'service' (safe non-user default).
|
|
74
|
+
*/
|
|
75
|
+
export function deriveActorType(subjectKind) {
|
|
76
|
+
if (!subjectKind)
|
|
77
|
+
return "service";
|
|
78
|
+
const normalized = subjectKind.toLowerCase();
|
|
79
|
+
if (normalized === "customer")
|
|
80
|
+
return "user";
|
|
81
|
+
if (ACTOR_TYPES.includes(normalized)) {
|
|
82
|
+
return normalized;
|
|
83
|
+
}
|
|
84
|
+
return "service";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build a `NodiiContext` from verified claims + the inbound request_id.
|
|
88
|
+
* Pure + deterministic given a fixed request_id; the adapters call it
|
|
89
|
+
* after extracting the request_id (so the generated-uuid path is
|
|
90
|
+
* observable/testable).
|
|
91
|
+
*/
|
|
92
|
+
export function mapClaimsToContext(claims, opts = {}) {
|
|
93
|
+
const actor_type = deriveActorType(claims.subjectKind);
|
|
94
|
+
const tenant_id = claims.tenantId ?? opts.tenantFallback;
|
|
95
|
+
if (!tenant_id) {
|
|
96
|
+
throw new Error("Cannot set NodiiContext: no tenant_id in claims and no tenantFallback. " +
|
|
97
|
+
"RLS + every signal key off tenant_id, so a tenant-less context is unsafe.");
|
|
98
|
+
}
|
|
99
|
+
// request_id: read x-request-id, generate a uuidv7 when absent.
|
|
100
|
+
const request_id = opts.requestId && opts.requestId.length > 0 ? opts.requestId : uuidv7();
|
|
101
|
+
const role = collapseRolesToHighest(claims.roles, opts.rolePrivilegeOrder, opts.roleFallback);
|
|
102
|
+
return {
|
|
103
|
+
tenant_id,
|
|
104
|
+
// user_id is null unless actor_type === 'user' (type invariant).
|
|
105
|
+
user_id: actor_type === "user" ? (claims.subjectId ?? null) : null,
|
|
106
|
+
actor_type,
|
|
107
|
+
on_behalf_of: claims.onBehalfOf ?? null,
|
|
108
|
+
role,
|
|
109
|
+
request_id,
|
|
110
|
+
intent_id: claims.intentId ?? null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../../src/context-adapter/claims.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type AdapterClaims, type MapClaimsOptions } from "./claims.js";
|
|
2
|
+
/** Canonical inbound request-id metadata key. */
|
|
3
|
+
export declare const REQUEST_ID_METADATA = "x-request-id";
|
|
4
|
+
/**
|
|
5
|
+
* Minimal structural shape of a gRPC unary call this adapter reads. A
|
|
6
|
+
* real `@grpc/grpc-js` `ServerUnaryCall` (and the `@nodii/grpc-auth`
|
|
7
|
+
* `withAuthUnary` augmentation) is assignable to it; so is a synthetic
|
|
8
|
+
* test fixture. Telemetry is a foundation lib, so it declares this
|
|
9
|
+
* locally instead of importing `@nodii/grpc-interceptors` (no cycle).
|
|
10
|
+
*/
|
|
11
|
+
export interface GrpcUnaryCall {
|
|
12
|
+
metadata?: {
|
|
13
|
+
get(key: string): readonly unknown[] | undefined;
|
|
14
|
+
};
|
|
15
|
+
/** Attached by the upstream auth interceptor (`@nodii/grpc-auth`). */
|
|
16
|
+
auth?: {
|
|
17
|
+
serviceActor?: {
|
|
18
|
+
service_id?: string;
|
|
19
|
+
serviceId?: string;
|
|
20
|
+
tenant_id?: string;
|
|
21
|
+
} | null;
|
|
22
|
+
userActor?: {
|
|
23
|
+
user_id?: string;
|
|
24
|
+
tenant_id?: string;
|
|
25
|
+
user_role?: string;
|
|
26
|
+
} | null;
|
|
27
|
+
requestContext?: {
|
|
28
|
+
tenant_id?: string;
|
|
29
|
+
intent_id?: string;
|
|
30
|
+
actor_type?: string;
|
|
31
|
+
} | null;
|
|
32
|
+
} | null;
|
|
33
|
+
request?: unknown;
|
|
34
|
+
}
|
|
35
|
+
export type GrpcUnaryHandler = (call: GrpcUnaryCall) => Promise<unknown>;
|
|
36
|
+
export type GrpcUnaryInterceptor = (methodName: string, handler: GrpcUnaryHandler) => GrpcUnaryHandler;
|
|
37
|
+
export interface NodiiContextInterceptorOptions extends Pick<MapClaimsOptions, "rolePrivilegeOrder" | "roleFallback" | "tenantFallback"> {
|
|
38
|
+
/**
|
|
39
|
+
* Read the verified claims off the call. Defaults to reading the
|
|
40
|
+
* `call.auth` envelope (userActor / serviceActor / requestContext) the
|
|
41
|
+
* `@nodii/grpc-auth` interceptor attaches, plus the `x-user-roles`
|
|
42
|
+
* metadata when present. Override to read a custom envelope.
|
|
43
|
+
*/
|
|
44
|
+
extractClaims?: (call: GrpcUnaryCall) => AdapterClaims | null;
|
|
45
|
+
/** Metadata key for the inbound request id. Defaults to `x-request-id`. */
|
|
46
|
+
requestIdMetadata?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Metadata key carrying a comma-separated roles list. Defaults to
|
|
49
|
+
* `x-user-roles`. Lets the gRPC seam receive `roles[]` (the
|
|
50
|
+
* `UserActorEnvelope` only carries a single `user_role`).
|
|
51
|
+
*/
|
|
52
|
+
rolesMetadata?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* gRPC server interceptor that SETS the active `NodiiContext` for the
|
|
56
|
+
* handler scope. Composes as a `(methodName, handler) => handler` wrapper
|
|
57
|
+
* (the `@nodii/grpc-interceptors` convention) and runs the wrapped
|
|
58
|
+
* handler inside `withContext` so every read of `getContext()` inside the
|
|
59
|
+
* handler — and any async work it awaits — sees the mapped context.
|
|
60
|
+
*/
|
|
61
|
+
export declare function nodiiContextInterceptor(options?: NodiiContextInterceptorOptions): GrpcUnaryInterceptor;
|
|
62
|
+
//# sourceMappingURL=grpc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc.d.ts","sourceRoot":"","sources":["../../src/context-adapter/grpc.ts"],"names":[],"mappings":"AAgBA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAC;AAErB,iDAAiD;AACjD,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE;QACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;KAClD,CAAC;IACF,sEAAsE;IACtE,IAAI,CAAC,EAAE;QACL,YAAY,CAAC,EAAE;YACb,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,GAAG,IAAI,CAAC;QACT,SAAS,CAAC,EAAE;YACV,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,GAAG,IAAI,CAAC;QACT,cAAc,CAAC,EAAE;YACf,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB,GAAG,IAAI,CAAC;KACV,GAAG,IAAI,CAAC;IACT,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AACzE,MAAM,MAAM,oBAAoB,GAAG,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,KACtB,gBAAgB,CAAC;AAEtB,MAAM,WAAW,8BACf,SAAQ,IAAI,CACV,gBAAgB,EAChB,oBAAoB,GAAG,cAAc,GAAG,gBAAgB,CACzD;IACD;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,aAAa,GAAG,IAAI,CAAC;IAC9D,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA4ED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,8BAAmC,GAC3C,oBAAoB,CAsBtB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// `@nodii/telemetry/context` — gRPC server entry-seam adapter (D44 § 6.5,
|
|
2
|
+
// D405).
|
|
3
|
+
//
|
|
4
|
+
// A handler-wrapping unary interceptor `(methodName, handler) => handler`
|
|
5
|
+
// (the `@nodii/grpc-auth` / `@nodii/grpc-interceptors` server-interceptor
|
|
6
|
+
// convention — `@grpc/grpc-js` has no native server-interceptor API, so
|
|
7
|
+
// services compose handler wrappers). It reads inbound metadata + the
|
|
8
|
+
// `call.auth` envelope attached by the auth interceptor and runs the
|
|
9
|
+
// handler inside a `withContext` scope so `getContext()` resolves the
|
|
10
|
+
// per-request `NodiiContext` for the whole handler.
|
|
11
|
+
//
|
|
12
|
+
// It is an INBOUND seam: it SETS context for the handler scope and makes
|
|
13
|
+
// no outbound peer calls.
|
|
14
|
+
import { withContext } from "../context.js";
|
|
15
|
+
import { mapClaimsToContext, } from "./claims.js";
|
|
16
|
+
/** Canonical inbound request-id metadata key. */
|
|
17
|
+
export const REQUEST_ID_METADATA = "x-request-id";
|
|
18
|
+
/** Read the first value of a metadata key as a UTF-8 string. */
|
|
19
|
+
function readMetadata(call, key) {
|
|
20
|
+
const values = call.metadata?.get(key);
|
|
21
|
+
if (!values || values.length === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
const first = values[0];
|
|
24
|
+
if (typeof first === "string")
|
|
25
|
+
return first;
|
|
26
|
+
if (first instanceof Uint8Array || Buffer.isBuffer?.(first)) {
|
|
27
|
+
try {
|
|
28
|
+
return Buffer.from(first).toString("utf8");
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Default claim extractor for the gRPC seam. Prefers the user actor
|
|
38
|
+
* (interactive principal) over the service actor (S2S caller). Reads
|
|
39
|
+
* `roles[]` from `x-user-roles` metadata when present, else falls back to
|
|
40
|
+
* the single `userActor.user_role`.
|
|
41
|
+
*/
|
|
42
|
+
function defaultExtractClaims(call, rolesMetadata) {
|
|
43
|
+
const auth = call.auth ?? null;
|
|
44
|
+
const userActor = auth?.userActor ?? null;
|
|
45
|
+
const serviceActor = auth?.serviceActor ?? null;
|
|
46
|
+
const requestContext = auth?.requestContext ?? null;
|
|
47
|
+
if (!userActor && !serviceActor && !requestContext)
|
|
48
|
+
return null;
|
|
49
|
+
// roles[]: prefer explicit metadata, else lift the singular user_role.
|
|
50
|
+
const rolesRaw = readMetadata(call, rolesMetadata);
|
|
51
|
+
const roles = rolesRaw
|
|
52
|
+
? rolesRaw
|
|
53
|
+
.split(",")
|
|
54
|
+
.map((r) => r.trim())
|
|
55
|
+
.filter((r) => r.length > 0)
|
|
56
|
+
: userActor?.user_role
|
|
57
|
+
? [userActor.user_role]
|
|
58
|
+
: null;
|
|
59
|
+
// Delegation (on_behalf_of / act / obo): the gRPC seam carries it in the
|
|
60
|
+
// `x-on-behalf-of` metadata header (symmetric with the Python + Go gRPC
|
|
61
|
+
// adapters). Honoured for BOTH branches — agent-delegation must NOT be
|
|
62
|
+
// silently dropped on the gRPC seam the way it is honoured on Hono.
|
|
63
|
+
const onBehalfOf = readMetadata(call, "x-on-behalf-of") ?? null;
|
|
64
|
+
if (userActor) {
|
|
65
|
+
return {
|
|
66
|
+
tenantId: userActor.tenant_id ?? requestContext?.tenant_id ?? null,
|
|
67
|
+
subjectId: userActor.user_id ?? null,
|
|
68
|
+
// Honour the auth interceptor's actor_type when it stamped one
|
|
69
|
+
// (e.g. 'agent' for a delegated principal); default to 'user' for
|
|
70
|
+
// a plain user-actor envelope. deriveActorType maps the value.
|
|
71
|
+
subjectKind: requestContext?.actor_type ?? "user",
|
|
72
|
+
roles,
|
|
73
|
+
onBehalfOf,
|
|
74
|
+
intentId: requestContext?.intent_id ?? null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// S2S-only call: a service principal acting with no user actor.
|
|
78
|
+
return {
|
|
79
|
+
tenantId: serviceActor?.tenant_id ?? requestContext?.tenant_id ?? null,
|
|
80
|
+
subjectId: serviceActor?.serviceId ?? serviceActor?.service_id ?? null,
|
|
81
|
+
subjectKind: requestContext?.actor_type ?? "service",
|
|
82
|
+
roles,
|
|
83
|
+
onBehalfOf,
|
|
84
|
+
intentId: requestContext?.intent_id ?? null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* gRPC server interceptor that SETS the active `NodiiContext` for the
|
|
89
|
+
* handler scope. Composes as a `(methodName, handler) => handler` wrapper
|
|
90
|
+
* (the `@nodii/grpc-interceptors` convention) and runs the wrapped
|
|
91
|
+
* handler inside `withContext` so every read of `getContext()` inside the
|
|
92
|
+
* handler — and any async work it awaits — sees the mapped context.
|
|
93
|
+
*/
|
|
94
|
+
export function nodiiContextInterceptor(options = {}) {
|
|
95
|
+
const requestIdMetadata = options.requestIdMetadata ?? REQUEST_ID_METADATA;
|
|
96
|
+
const rolesMetadata = options.rolesMetadata ?? "x-user-roles";
|
|
97
|
+
const extract = options.extractClaims ??
|
|
98
|
+
((call) => defaultExtractClaims(call, rolesMetadata));
|
|
99
|
+
return (_methodName, handler) => {
|
|
100
|
+
return (call) => {
|
|
101
|
+
const claims = extract(call) ?? {};
|
|
102
|
+
const requestId = readMetadata(call, requestIdMetadata) ?? null;
|
|
103
|
+
const ctx = mapClaimsToContext(claims, {
|
|
104
|
+
requestId,
|
|
105
|
+
rolePrivilegeOrder: options.rolePrivilegeOrder,
|
|
106
|
+
roleFallback: options.roleFallback,
|
|
107
|
+
tenantFallback: options.tenantFallback,
|
|
108
|
+
});
|
|
109
|
+
return withContext(ctx, () => handler(call));
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=grpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc.js","sourceRoot":"","sources":["../../src/context-adapter/grpc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler } from "hono";
|
|
2
|
+
import { type AdapterClaims, type MapClaimsOptions } from "./claims.js";
|
|
3
|
+
/** Canonical inbound request-id header (lower-case; Hono headers are case-insensitive). */
|
|
4
|
+
export declare const REQUEST_ID_HEADER = "x-request-id";
|
|
5
|
+
export interface NodiiContextMiddlewareOptions extends Pick<MapClaimsOptions, "rolePrivilegeOrder" | "roleFallback" | "tenantFallback"> {
|
|
6
|
+
/**
|
|
7
|
+
* Where to read the verified claims from the Hono context. Defaults to
|
|
8
|
+
* reading `c.get("nodiiClaims")` (the `@nodii/auth-sdk/hono` convention)
|
|
9
|
+
* and adapting it. Override when claims live under a different key or in
|
|
10
|
+
* a different shape.
|
|
11
|
+
*/
|
|
12
|
+
extractClaims?: (c: Context) => AdapterClaims | null;
|
|
13
|
+
/**
|
|
14
|
+
* Header to read the inbound request id from. Defaults to
|
|
15
|
+
* `x-request-id`. When the header is absent a uuidv7 is generated.
|
|
16
|
+
*/
|
|
17
|
+
requestIdHeader?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hono middleware that SETS the active `NodiiContext` for the downstream
|
|
21
|
+
* request scope. Reads verified claims (default: `c.get("nodiiClaims")`)
|
|
22
|
+
* + the `x-request-id` header, applies the locked field mappings, and
|
|
23
|
+
* runs the rest of the request inside a `withContext` scope.
|
|
24
|
+
*
|
|
25
|
+
* The context is SCOPED to the single request via `withContext` (which
|
|
26
|
+
* unwinds the AsyncLocalStorage frame when `next()` settles) — NOT a bare
|
|
27
|
+
* `setContextForCurrentScope`. A bare `enterWith` mutates the current ALS
|
|
28
|
+
* frame and never unwinds, so if the middleware is mounted at the app root
|
|
29
|
+
* (no per-request ALS frame opened first) request N's context would bleed
|
|
30
|
+
* into request N+1. `withContext` is symmetric with the gRPC interceptor
|
|
31
|
+
* and guarantees `getContext()` is unset again once the request settles.
|
|
32
|
+
*
|
|
33
|
+
* A tenant-less request (no `tenant_id` claim + no `tenantFallback`) is
|
|
34
|
+
* rejected with `400 missing_tenant_context` — a context with no tenant is
|
|
35
|
+
* unsafe to run a handler under (RLS + every signal key off `tenant_id`).
|
|
36
|
+
* Symmetric with the Go HTTP middleware (`400`) and the Python adapter.
|
|
37
|
+
*/
|
|
38
|
+
export declare function nodiiContextMiddleware(options?: NodiiContextMiddlewareOptions): MiddlewareHandler;
|
|
39
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/context-adapter/hono.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAC;AAErB,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,MAAM,WAAW,6BACf,SAAQ,IAAI,CACV,gBAAgB,EAChB,oBAAoB,GAAG,cAAc,GAAG,gBAAgB,CACzD;IACD;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,aAAa,GAAG,IAAI,CAAC;IACrD;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA6CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,iBAAiB,CA6BnB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// `@nodii/telemetry/context` — Hono entry-seam adapter (D44 § 6.5, D405).
|
|
2
|
+
//
|
|
3
|
+
// The Hono middleware that SETS the per-request `NodiiContext` on entry.
|
|
4
|
+
// Place it AFTER auth (so `c.get("nodiiClaims")` is populated) and before
|
|
5
|
+
// any handler that reads `getContext()`:
|
|
6
|
+
//
|
|
7
|
+
// import { requireAuth } from "@nodii/auth-sdk/hono";
|
|
8
|
+
// import { nodiiContextMiddleware } from "@nodii/telemetry/context";
|
|
9
|
+
//
|
|
10
|
+
// app.use("/api/*", requireAuth({ verifier }));
|
|
11
|
+
// app.use("/api/*", nodiiContextMiddleware());
|
|
12
|
+
// app.get("/api/me", (c) => c.json(getContext()));
|
|
13
|
+
//
|
|
14
|
+
// It is an INBOUND seam: it sets context for the downstream scope and
|
|
15
|
+
// makes no outbound peer calls.
|
|
16
|
+
import { withContext } from "../context.js";
|
|
17
|
+
import { mapClaimsToContext, } from "./claims.js";
|
|
18
|
+
/** Canonical inbound request-id header (lower-case; Hono headers are case-insensitive). */
|
|
19
|
+
export const REQUEST_ID_HEADER = "x-request-id";
|
|
20
|
+
/**
|
|
21
|
+
* Default claim extractor: reads `c.get("nodiiClaims")` and maps the
|
|
22
|
+
* auth-sdk NodiiClaims surface onto `AdapterClaims`. Returns null when no
|
|
23
|
+
* claims are present (unauthenticated route) — the middleware then sets a
|
|
24
|
+
* minimal context from headers + the tenantFallback so downstream code
|
|
25
|
+
* still has a request_id-bearing context.
|
|
26
|
+
*/
|
|
27
|
+
function defaultExtractClaims(c) {
|
|
28
|
+
// `c.get` is typed against the app Env; cast through unknown so this
|
|
29
|
+
// adapter works regardless of the consumer's declared Variables.
|
|
30
|
+
const raw = c.get("nodiiClaims");
|
|
31
|
+
if (!raw)
|
|
32
|
+
return null;
|
|
33
|
+
return {
|
|
34
|
+
tenantId: raw.tenantId ?? null,
|
|
35
|
+
subjectId: raw.sub ?? null,
|
|
36
|
+
subjectKind: raw.principalKind ?? null,
|
|
37
|
+
roles: raw.roles ?? null,
|
|
38
|
+
onBehalfOf: raw.onBehalfOf ?? raw.impersonator?.sub ?? null,
|
|
39
|
+
intentId: raw.intentId ?? null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Hono middleware that SETS the active `NodiiContext` for the downstream
|
|
44
|
+
* request scope. Reads verified claims (default: `c.get("nodiiClaims")`)
|
|
45
|
+
* + the `x-request-id` header, applies the locked field mappings, and
|
|
46
|
+
* runs the rest of the request inside a `withContext` scope.
|
|
47
|
+
*
|
|
48
|
+
* The context is SCOPED to the single request via `withContext` (which
|
|
49
|
+
* unwinds the AsyncLocalStorage frame when `next()` settles) — NOT a bare
|
|
50
|
+
* `setContextForCurrentScope`. A bare `enterWith` mutates the current ALS
|
|
51
|
+
* frame and never unwinds, so if the middleware is mounted at the app root
|
|
52
|
+
* (no per-request ALS frame opened first) request N's context would bleed
|
|
53
|
+
* into request N+1. `withContext` is symmetric with the gRPC interceptor
|
|
54
|
+
* and guarantees `getContext()` is unset again once the request settles.
|
|
55
|
+
*
|
|
56
|
+
* A tenant-less request (no `tenant_id` claim + no `tenantFallback`) is
|
|
57
|
+
* rejected with `400 missing_tenant_context` — a context with no tenant is
|
|
58
|
+
* unsafe to run a handler under (RLS + every signal key off `tenant_id`).
|
|
59
|
+
* Symmetric with the Go HTTP middleware (`400`) and the Python adapter.
|
|
60
|
+
*/
|
|
61
|
+
export function nodiiContextMiddleware(options = {}) {
|
|
62
|
+
const extract = options.extractClaims ?? defaultExtractClaims;
|
|
63
|
+
const requestIdHeader = options.requestIdHeader ?? REQUEST_ID_HEADER;
|
|
64
|
+
return async (c, next) => {
|
|
65
|
+
const claims = extract(c) ?? {};
|
|
66
|
+
const requestId = c.req.header(requestIdHeader) ?? null;
|
|
67
|
+
let ctx;
|
|
68
|
+
try {
|
|
69
|
+
ctx = mapClaimsToContext(claims, {
|
|
70
|
+
requestId,
|
|
71
|
+
rolePrivilegeOrder: options.rolePrivilegeOrder,
|
|
72
|
+
roleFallback: options.roleFallback,
|
|
73
|
+
tenantFallback: options.tenantFallback,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// The only throw from mapClaimsToContext is the tenant-less guard.
|
|
78
|
+
// Fail closed with a 400 (do NOT run the handler with no context).
|
|
79
|
+
return c.json({ error: true, code: "missing_tenant_context" }, 400);
|
|
80
|
+
}
|
|
81
|
+
// Scope the context to this request: established for the downstream
|
|
82
|
+
// scope, removed after `next()` settles.
|
|
83
|
+
return withContext(ctx, () => next());
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/context-adapter/hono.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,yEAAyE;AACzE,0EAA0E;AAC1E,yCAAyC;AACzC,EAAE;AACF,wDAAwD;AACxD,uEAAuE;AACvE,EAAE;AACF,kDAAkD;AAClD,iDAAiD;AACjD,qDAAqD;AACrD,EAAE;AACF,sEAAsE;AACtE,gCAAgC;AAGhC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAGL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,2FAA2F;AAC3F,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAwChD;;;;;;GAMG;AACH,SAAS,oBAAoB,CAAC,CAAU;IACtC,qEAAqE;IACrE,iEAAiE;IACjE,MAAM,GAAG,GAAI,CAA4C,CAAC,GAAG,CAC3D,aAAa,CACiB,CAAC;IACjC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO;QACL,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI;QAC9B,SAAS,EAAE,GAAG,CAAC,GAAG,IAAI,IAAI;QAC1B,WAAW,EAAE,GAAG,CAAC,aAAa,IAAI,IAAI;QACtC,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI;QACxB,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,YAAY,EAAE,GAAG,IAAI,IAAI;QAC3D,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI;KAC/B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAyC,EAAE;IAE3C,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,IAAI,oBAAoB,CAAC;IAC9D,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,iBAAiB,CAAC;IAErE,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC;QAExD,IAAI,GAAiB,CAAC;QACtB,IAAI,CAAC;YACH,GAAG,GAAG,kBAAkB,CAAC,MAAM,EAAE;gBAC/B,SAAS;gBACT,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;gBAC9C,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,cAAc,EAAE,OAAO,CAAC,cAAc;aACvC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,mEAAmE;YACnE,OAAO,CAAC,CAAC,IAAI,CACX,EAAE,KAAK,EAAE,IAAa,EAAE,IAAI,EAAE,wBAAwB,EAAE,EACxD,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,oEAAoE;QACpE,yCAAyC;QACzC,OAAO,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { type AdapterClaims, type MapClaimsOptions, collapseRolesToHighest, deriveActorType, mapClaimsToContext, ROLE_PRIVILEGE_ORDER, } from "./claims.js";
|
|
2
|
+
export { type NodiiContextMiddlewareOptions, nodiiContextMiddleware, REQUEST_ID_HEADER, } from "./hono.js";
|
|
3
|
+
export { type GrpcUnaryCall, type GrpcUnaryHandler, type GrpcUnaryInterceptor, type NodiiContextInterceptorOptions, nodiiContextInterceptor, REQUEST_ID_METADATA, } from "./grpc.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context-adapter/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,6BAA6B,EAClC,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,8BAA8B,EACnC,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// `@nodii/telemetry/context` — entry-seam context adapters (D44 § 6.5,
|
|
2
|
+
// D405 context adoption; req b3b03fc5 / clarification c9906a51).
|
|
3
|
+
//
|
|
4
|
+
// These adapters SET the per-request `NodiiContext` at the entry seam:
|
|
5
|
+
// - `nodiiContextMiddleware` — Hono middleware (HTTP entry).
|
|
6
|
+
// - `nodiiContextInterceptor` — gRPC unary server interceptor.
|
|
7
|
+
//
|
|
8
|
+
// Both apply the operator-LOCKED field mappings (see `claims.ts`):
|
|
9
|
+
// actor_type ← subject kind; role ← highest-privilege of roles[];
|
|
10
|
+
// on_behalf_of ← delegation claim; request_id ← x-request-id || uuidv7().
|
|
11
|
+
//
|
|
12
|
+
// Lives behind a subpath so consumers who don't run an HTTP/gRPC entry
|
|
13
|
+
// seam (cron / sweeper flows) don't pull `hono`.
|
|
14
|
+
export { collapseRolesToHighest, deriveActorType, mapClaimsToContext, ROLE_PRIVILEGE_ORDER, } from "./claims.js";
|
|
15
|
+
export { nodiiContextMiddleware, REQUEST_ID_HEADER, } from "./hono.js";
|
|
16
|
+
export { nodiiContextInterceptor, REQUEST_ID_METADATA, } from "./grpc.js";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context-adapter/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,iEAAiE;AACjE,EAAE;AACF,uEAAuE;AACvE,+DAA+D;AAC/D,iEAAiE;AACjE,EAAE;AACF,mEAAmE;AACnE,oEAAoE;AACpE,4EAA4E;AAC5E,EAAE;AACF,uEAAuE;AACvE,iDAAiD;AAEjD,OAAO,EAGL,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAKL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,WAAW,CAAC"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EACV,oBAAoB,EACpB,IAAI,EACJ,QAAQ,EACR,WAAW,EACX,UAAU,EACV,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EACV,aAAa,EACb,WAAW,EACX,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,8BAA8B,EAC9B,KAAK,EACL,iBAAiB,EACjB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,SAAS,EACT,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,YAAY,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,UAAU,EACV,cAAc,EACd,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,YAAY,EACV,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,qBAAqB,EACrB,wBAAwB,EACxB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Spec: planning hub feature_doc serviceId=nodii-libs docKey=telemetry v3
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// - @nodii/telemetry/
|
|
8
|
-
//
|
|
9
|
-
// - @nodii/telemetry/
|
|
10
|
-
// - @nodii/telemetry/
|
|
5
|
+
// Subpath imports (the entry-seam adapters per D44 § 6.5 / D405). Each is
|
|
6
|
+
// a real, built, exported subpath — verified by the subpath-import test:
|
|
7
|
+
// - @nodii/telemetry/context — Hono middleware + gRPC server interceptor
|
|
8
|
+
// that SET NodiiContext at the entry seam.
|
|
9
|
+
// - @nodii/telemetry/outbox — Outbox emit + propagation-column adapter.
|
|
10
|
+
// - @nodii/telemetry/drizzle — Drizzle audit_event schema fragments.
|
|
11
|
+
//
|
|
12
|
+
// Not yet shipped (D44 § 6.5 lists them; no implementation exists yet —
|
|
13
|
+
// they are tracked as follow-on requests, NOT advertised as vapor):
|
|
14
|
+
// - bullmq (BullMQ producer/consumer context envelope, D42)
|
|
15
|
+
// - system-process (cron/sweeper synthetic context, § 6.6)
|
|
16
|
+
// - saga (saga-step context coupling)
|
|
11
17
|
//
|
|
12
18
|
// Top-level barrel re-exports the public surface so the common-case
|
|
13
19
|
// import works:
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,EAAE;AACF,0EAA0E;AAC1E,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,EAAE;AACF,0EAA0E;AAC1E,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,wEAAwE;AACxE,EAAE;AACF,wEAAwE;AACxE,oEAAoE;AACpE,uEAAuE;AACvE,8DAA8D;AAC9D,mDAAmD;AACnD,EAAE;AACF,oEAAoE;AACpE,gBAAgB;AAChB,EAAE;AACF,oEAAoE;AACpE,gEAAgE;AAChE,0DAA0D;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAWjD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAS5E,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,8BAA8B,EAC9B,KAAK,EACL,iBAAiB,EACjB,cAAc,GACf,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,MAAM,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,UAAU,EACV,cAAc,EACd,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AA0BrD,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/outbox/index.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,0BAA0B,EAC1B,gBAAgB,EAChB,UAAU,GACX,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// `@nodii/telemetry/outbox` — Outbox emit + propagation-column adapter
|
|
2
|
+
// (D43, communication-doctrine § 9.1).
|
|
3
|
+
//
|
|
4
|
+
// `emitOutbox(event)` stages an outbox row deriving the propagation
|
|
5
|
+
// columns (`tenant_id, request_id, intent_id, trace_parent, trace_state`)
|
|
6
|
+
// + `context_envelope` jsonb from the active `NodiiContext` + OTel span.
|
|
7
|
+
// `_setOutboxWriter` lets service init wire the per-service writer that
|
|
8
|
+
// persists the row inside the service's transaction.
|
|
9
|
+
//
|
|
10
|
+
// Lives behind a subpath so consumers who don't emit outbox rows don't
|
|
11
|
+
// load the helper.
|
|
12
|
+
export { _resetOutboxWriterForTests, _setOutboxWriter, emitOutbox, } from "../outbox-emit.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/outbox/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,uCAAuC;AACvC,EAAE;AACF,oEAAoE;AACpE,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,qDAAqD;AACrD,EAAE;AACF,uEAAuE;AACvE,mBAAmB;AAEnB,OAAO,EAIL,0BAA0B,EAC1B,gBAAgB,EAChB,UAAU,GACX,MAAM,mBAAmB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodii/telemetry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Substrate observability library for the Nodii microservice stack — OTel wrapper, log envelope, intent lifecycle, audit signal, agent registry, context propagation. Spec: planning hub docKey=telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
"./drizzle": {
|
|
15
15
|
"types": "./dist/drizzle/index.d.ts",
|
|
16
16
|
"default": "./dist/drizzle/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./context": {
|
|
19
|
+
"types": "./dist/context-adapter/index.d.ts",
|
|
20
|
+
"default": "./dist/context-adapter/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./outbox": {
|
|
23
|
+
"types": "./dist/outbox/index.d.ts",
|
|
24
|
+
"default": "./dist/outbox/index.js"
|
|
17
25
|
}
|
|
18
26
|
},
|
|
19
27
|
"files": [
|
|
@@ -47,10 +55,11 @@
|
|
|
47
55
|
"@opentelemetry/semantic-conventions": "^1.28.0"
|
|
48
56
|
},
|
|
49
57
|
"peerDependencies": {
|
|
50
|
-
"@nodii/audit-chain": "
|
|
58
|
+
"@nodii/audit-chain": ">=0.9.1 <1.0.0",
|
|
51
59
|
"@opentelemetry/api": "^1.9.0",
|
|
52
60
|
"@opentelemetry/api-logs": "^0.57.2",
|
|
53
|
-
"drizzle-orm": ">=0.36 <0.50"
|
|
61
|
+
"drizzle-orm": ">=0.36 <0.50",
|
|
62
|
+
"hono": "^4.0.0"
|
|
54
63
|
},
|
|
55
64
|
"peerDependenciesMeta": {
|
|
56
65
|
"@nodii/audit-chain": {
|
|
@@ -64,11 +73,15 @@
|
|
|
64
73
|
},
|
|
65
74
|
"drizzle-orm": {
|
|
66
75
|
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"hono": {
|
|
78
|
+
"optional": true
|
|
67
79
|
}
|
|
68
80
|
},
|
|
69
81
|
"devDependencies": {
|
|
70
|
-
"@nodii/audit-chain": "0.
|
|
82
|
+
"@nodii/audit-chain": "0.10.0",
|
|
71
83
|
"drizzle-orm": "^0.45.2",
|
|
84
|
+
"hono": "^4.4.0",
|
|
72
85
|
"postgres": "^3.4.0",
|
|
73
86
|
"typescript": "^5.9.3"
|
|
74
87
|
},
|