@tai42/api-client 0.1.0 → 0.1.1
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/README.md +1 -1
- package/dist/agents.d.ts +14 -14
- package/dist/client.d.ts +202 -55
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +54 -4
- package/dist/client.js.map +1 -1
- package/dist/http.d.ts +2 -3
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +2 -3
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +814 -222
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +145 -7
- package/dist/schemas.js.map +1 -1
- package/dist/tool-runs.d.ts +12 -12
- package/package.json +6 -6
- package/src/client.ts +107 -4
- package/src/http.ts +2 -3
- package/src/index.ts +17 -1
- package/src/schemas.ts +173 -7
package/src/client.ts
CHANGED
|
@@ -82,6 +82,23 @@ export interface TopicVerifierBody {
|
|
|
82
82
|
readonly config?: Record<string, unknown>;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Body for minting a trigger link (POST `/api/hooks/trigger-links`). `ttl_seconds`
|
|
87
|
+
* is REQUIRED and nullable — a positive int is a timed link, `null` is a permanent
|
|
88
|
+
* link (`0`/negative is a loud server 400); it is always sent EXPLICITLY (a dropped
|
|
89
|
+
* key 400s server-side). `name` is optional (the server generates one when omitted);
|
|
90
|
+
* `tool_kwargs` is the optional per-link params merged last into every fire.
|
|
91
|
+
*/
|
|
92
|
+
export interface TriggerLinkCreateBody {
|
|
93
|
+
readonly topic: string;
|
|
94
|
+
readonly name?: string;
|
|
95
|
+
readonly execution_key: string;
|
|
96
|
+
// The link is a token door; this is its only auth knob (token → token+api_key).
|
|
97
|
+
readonly require_api_key: boolean;
|
|
98
|
+
readonly ttl_seconds: number | null;
|
|
99
|
+
readonly tool_kwargs?: Record<string, unknown> | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
/** Body for adding a URL to a scope (POST `/api/auth/scopes`). */
|
|
86
103
|
export interface AddUrlToScopeBody {
|
|
87
104
|
readonly scope_id: string;
|
|
@@ -89,6 +106,31 @@ export interface AddUrlToScopeBody {
|
|
|
89
106
|
readonly pattern?: string;
|
|
90
107
|
}
|
|
91
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Body for creating a role (POST `/api/auth/roles`). `base_tier` is the security
|
|
111
|
+
* tier the role inherits (`editor`/`viewer` — `admin` is reserved); its jq is
|
|
112
|
+
* resolved server-side, never authored here. `grants` is the editable per-tag
|
|
113
|
+
* access-level map (feature-group tag → `none`/`read`/`write`); it defaults to
|
|
114
|
+
* empty (every group `none`, fail-closed) when omitted.
|
|
115
|
+
*/
|
|
116
|
+
export interface RoleCreateBody {
|
|
117
|
+
readonly name: string;
|
|
118
|
+
readonly description?: string;
|
|
119
|
+
readonly base_tier: string;
|
|
120
|
+
readonly grants?: Record<string, s.GrantLevel>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Body for editing a role (PUT `/api/auth/roles/{name}`). Both fields are
|
|
125
|
+
* omit-means-KEEP: an absent `grants` preserves the stored map (never a silent
|
|
126
|
+
* wipe), an absent `description` preserves the stored description. The base-tier jq
|
|
127
|
+
* is seed-fixed and not editable here.
|
|
128
|
+
*/
|
|
129
|
+
export interface RoleUpdateBody {
|
|
130
|
+
readonly grants?: Record<string, s.GrantLevel>;
|
|
131
|
+
readonly description?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
92
134
|
/**
|
|
93
135
|
* Body for pinning a route public (POST `/api/auth/public-routes`). `pattern` is an
|
|
94
136
|
* optional regex the AC verifier full-matches request paths against, mapping every
|
|
@@ -311,6 +353,13 @@ export function createApiClient(config: ApiConfig) {
|
|
|
311
353
|
) => apiRequest(config, path, schema, options);
|
|
312
354
|
|
|
313
355
|
return {
|
|
356
|
+
// The configured API base — the exact `baseUrl` this client was constructed
|
|
357
|
+
// with (`''` for a same-origin deployment). Read-only: features that must
|
|
358
|
+
// compose an ABSOLUTE API URL (a QR-encoded public path the API origin must
|
|
359
|
+
// receive, not the SPA origin) read it instead of assuming same-origin. The
|
|
360
|
+
// transport itself still joins it per request; this only surfaces the value.
|
|
361
|
+
baseUrl: config.baseUrl ?? '',
|
|
362
|
+
|
|
314
363
|
// -- tools ---------------------------------------------------------------
|
|
315
364
|
listTools: (signal?: AbortSignal) => req('/api/tools', s.toolNames, { signal }),
|
|
316
365
|
getToolSchema: (name: string, signal?: AbortSignal) =>
|
|
@@ -579,6 +628,32 @@ export function createApiClient(config: ApiConfig) {
|
|
|
579
628
|
method: 'DELETE',
|
|
580
629
|
}),
|
|
581
630
|
|
|
631
|
+
// -- hooks: trigger links -------------------------------------------------
|
|
632
|
+
// Mint a trigger link for a topic. `ttl_seconds` is sent EXPLICITLY
|
|
633
|
+
// (including `null` for a permanent link) — the server requires the key present
|
|
634
|
+
// and 400s a dropped one; `tool_kwargs` is omitted when undefined. The raw
|
|
635
|
+
// `token` rides the response ONCE (composed into the QR URL client-side).
|
|
636
|
+
createTriggerLink: (body: TriggerLinkCreateBody) =>
|
|
637
|
+
req('/api/hooks/trigger-links', s.triggerLinkCreated, {
|
|
638
|
+
method: 'POST',
|
|
639
|
+
body: {
|
|
640
|
+
topic: body.topic,
|
|
641
|
+
name: body.name,
|
|
642
|
+
execution_key: body.execution_key,
|
|
643
|
+
require_api_key: body.require_api_key,
|
|
644
|
+
ttl_seconds: body.ttl_seconds,
|
|
645
|
+
tool_kwargs: body.tool_kwargs,
|
|
646
|
+
},
|
|
647
|
+
}),
|
|
648
|
+
// Every live trigger-link record (hash prefix only — never a raw token).
|
|
649
|
+
listTriggerLinks: (signal?: AbortSignal) =>
|
|
650
|
+
req('/api/hooks/trigger-links', s.triggerLinkList, { signal }),
|
|
651
|
+
// Revoke a link by name (immediate + durable). 404 when the name is unknown.
|
|
652
|
+
deleteTriggerLink: (name: string) =>
|
|
653
|
+
req(`/api/hooks/trigger-links/${encodeSegment(name)}`, s.triggerLinkDeleted, {
|
|
654
|
+
method: 'DELETE',
|
|
655
|
+
}),
|
|
656
|
+
|
|
582
657
|
// -- mcp config schema (manifest McpTab form) ----------------------------
|
|
583
658
|
getMcpConfigSchema: (signal?: AbortSignal) =>
|
|
584
659
|
req('/api/mcp-config/schema', s.jsonSchema, { signal }),
|
|
@@ -610,6 +685,35 @@ export function createApiClient(config: ApiConfig) {
|
|
|
610
685
|
unpinPublicRoute: (url: string) =>
|
|
611
686
|
req('/api/auth/public-routes', s.unpinPublicResult, { method: 'DELETE', body: { url } }),
|
|
612
687
|
|
|
688
|
+
// -- auth: roles (editable per-tag RBAC) ---------------------------------
|
|
689
|
+
// The seeded + operator-authored roles as full bodies (base-tier ceiling +
|
|
690
|
+
// editable per-tag grant map). ADMIN-ONLY — the route is `secret` (a listing
|
|
691
|
+
// exposes each role's raw base-tier jq); a non-admin projection never reaches it.
|
|
692
|
+
listRoles: (signal?: AbortSignal) => req('/api/auth/roles', s.roleList, { signal }),
|
|
693
|
+
// Create an operator-authored role. The server validates the grant map + base
|
|
694
|
+
// tier and 409s on a name collision; the reserved `admin` role cannot be created.
|
|
695
|
+
createRole: (body: RoleCreateBody) =>
|
|
696
|
+
req('/api/auth/roles', s.roleBody, { method: 'POST', body }),
|
|
697
|
+
// Edit a role's per-tag grant map + description (omit-means-keep). The reserved
|
|
698
|
+
// `admin` role and any `allow_all` role are block-downgrade guarded server-side.
|
|
699
|
+
updateRole: (name: string, body: RoleUpdateBody) =>
|
|
700
|
+
req(`/api/auth/roles/${encodeSegment(name)}`, s.roleBody, { method: 'PUT', body }),
|
|
701
|
+
// Delete a role. The reserved `admin` role is undeletable; a role still assigned
|
|
702
|
+
// to any principal is a loud 409.
|
|
703
|
+
deleteRole: (name: string) =>
|
|
704
|
+
req(`/api/auth/roles/${encodeSegment(name)}`, s.roleDeleted, { method: 'DELETE' }),
|
|
705
|
+
// A role's append-only version history + who/action/before→after audit trail.
|
|
706
|
+
// ADMIN-ONLY (`secret`).
|
|
707
|
+
listRoleVersions: (name: string, signal?: AbortSignal) =>
|
|
708
|
+
req(`/api/auth/roles/${encodeSegment(name)}/versions`, s.roleVersions, { signal }),
|
|
709
|
+
// Re-point a role's active version to a prior one (LIVE — holders follow on
|
|
710
|
+
// their next request); returns the re-pointed role body.
|
|
711
|
+
rollbackRole: (name: string, version: number) =>
|
|
712
|
+
req(`/api/auth/roles/${encodeSegment(name)}/rollback`, s.roleBody, {
|
|
713
|
+
method: 'POST',
|
|
714
|
+
body: { version },
|
|
715
|
+
}),
|
|
716
|
+
|
|
613
717
|
// -- auth: api keys ------------------------------------------------------
|
|
614
718
|
listTokensPayload: (signal?: AbortSignal) =>
|
|
615
719
|
req('/api/auth/tokens-payload', s.tokensPayload, { signal }),
|
|
@@ -738,8 +842,8 @@ export function createApiClient(config: ApiConfig) {
|
|
|
738
842
|
req(`/api/schedules/${encodeSegment(name)}`, s.jsonValue, { method: 'DELETE' }),
|
|
739
843
|
|
|
740
844
|
// -- observability -------------------------------------------------------
|
|
741
|
-
//
|
|
742
|
-
//
|
|
845
|
+
// The monitoring-backend dashboard rollups (`GET /api/observability/metrics`),
|
|
846
|
+
// a JSON aggregate distinct from any raw process-metrics text endpoint.
|
|
743
847
|
getObservabilityMetrics: (params: MetricsQuery = {}, signal?: AbortSignal) =>
|
|
744
848
|
req('/api/observability/metrics', s.dashboardMetrics, {
|
|
745
849
|
query: { from: params.from, to: params.to, granularity: params.granularity },
|
|
@@ -801,9 +905,8 @@ export function createApiClient(config: ApiConfig) {
|
|
|
801
905
|
getMarketplaceAdvisories: (signal?: AbortSignal) =>
|
|
802
906
|
req('/api/marketplace/advisories', s.marketplaceAdvisories, { signal }),
|
|
803
907
|
|
|
804
|
-
// -- system (plain-text ops
|
|
908
|
+
// -- system (plain-text ops endpoint) ------------------------------------
|
|
805
909
|
getHealth: (signal?: AbortSignal) => apiText(config, '/health', { signal }),
|
|
806
|
-
getMetrics: (signal?: AbortSignal) => apiText(config, '/metrics', { signal }),
|
|
807
910
|
|
|
808
911
|
// The pluggable-kind status table (`GET /api/system/kinds`) — a JSON `{data}`
|
|
809
912
|
// envelope, one row per kind with its active/default/off state.
|
package/src/http.ts
CHANGED
|
@@ -129,9 +129,8 @@ export async function apiRequest<S extends z.ZodTypeAny>(
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* Fetch a plain-text endpoint (the skeleton's `/health` → `"OK"`
|
|
133
|
-
*
|
|
134
|
-
* verbatim. Non-2xx still throws loudly.
|
|
132
|
+
* Fetch a plain-text endpoint (the skeleton's `/health` → `"OK"`). No `{ data }`
|
|
133
|
+
* envelope, no zod — the body is returned verbatim. Non-2xx still throws loudly.
|
|
135
134
|
*/
|
|
136
135
|
export async function apiText(
|
|
137
136
|
config: ApiConfig,
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @tai42/api-client — the typed transport for the
|
|
2
|
+
* @tai42/api-client — the typed transport for the tai42-skeleton HTTP API.
|
|
3
3
|
*
|
|
4
4
|
* One zod schema per consumed endpoint; every response is
|
|
5
5
|
* validated and every failure throws. No caching of its own — server state
|
|
@@ -14,7 +14,10 @@ export type {
|
|
|
14
14
|
ClaimLinkBody,
|
|
15
15
|
ValidateConditionBody,
|
|
16
16
|
TopicVerifierBody,
|
|
17
|
+
TriggerLinkCreateBody,
|
|
17
18
|
AddUrlToScopeBody,
|
|
19
|
+
RoleCreateBody,
|
|
20
|
+
RoleUpdateBody,
|
|
18
21
|
PinRoutePublicBody,
|
|
19
22
|
CreatePresetBody,
|
|
20
23
|
SavePresetVersionBody,
|
|
@@ -67,6 +70,13 @@ export type {
|
|
|
67
70
|
ToolMediaResult,
|
|
68
71
|
ToolTagEntry,
|
|
69
72
|
AuthRoute,
|
|
73
|
+
RouteAction,
|
|
74
|
+
GrantLevel,
|
|
75
|
+
RoleGrants,
|
|
76
|
+
RoleBody,
|
|
77
|
+
RoleVersion,
|
|
78
|
+
RoleAuditEvent,
|
|
79
|
+
RoleVersions,
|
|
70
80
|
PresetRecord,
|
|
71
81
|
PresetDetail,
|
|
72
82
|
PresetBody,
|
|
@@ -95,6 +105,8 @@ export type {
|
|
|
95
105
|
Notification,
|
|
96
106
|
Notifications,
|
|
97
107
|
HookParams,
|
|
108
|
+
HookList,
|
|
109
|
+
TriggerAuth,
|
|
98
110
|
SettingsSchema,
|
|
99
111
|
TokensPayload,
|
|
100
112
|
LoginFormField,
|
|
@@ -106,6 +118,10 @@ export type {
|
|
|
106
118
|
PatternEntry,
|
|
107
119
|
SubMcpEntry,
|
|
108
120
|
ClaimLinkCreated,
|
|
121
|
+
TriggerLinkCreated,
|
|
122
|
+
TriggerLinkRecord,
|
|
123
|
+
TriggerLinkList,
|
|
124
|
+
TriggerLinkDeleted,
|
|
109
125
|
LogoutResult,
|
|
110
126
|
AuthCapabilities,
|
|
111
127
|
BackupDocument,
|
package/src/schemas.ts
CHANGED
|
@@ -679,14 +679,26 @@ export const notifications = z.object({
|
|
|
679
679
|
export type Notifications = z.infer<typeof notifications>;
|
|
680
680
|
|
|
681
681
|
// -- hooks -------------------------------------------------------------------
|
|
682
|
-
// Shapes match
|
|
682
|
+
// Shapes match tai42_contract.hooks.HookParams: a hook fires a `tool` on a `topic`,
|
|
683
683
|
// optionally gated by a `condition` and shaped by an `expr` (each either an inline
|
|
684
684
|
// spec or a registered id, with its own kwargs).
|
|
685
685
|
|
|
686
|
+
/** A fire door — who may trigger a path, not what the fire may do. A string enum. */
|
|
687
|
+
export const triggerAuth = z.enum([
|
|
688
|
+
'public',
|
|
689
|
+
'verifier',
|
|
690
|
+
'token',
|
|
691
|
+
'token+api_key',
|
|
692
|
+
'out-of-service',
|
|
693
|
+
]);
|
|
694
|
+
export type TriggerAuth = z.infer<typeof triggerAuth>;
|
|
695
|
+
|
|
686
696
|
export const hookParams = z.object({
|
|
687
697
|
name: z.string(),
|
|
688
698
|
topic: z.string(),
|
|
689
699
|
tool: z.string(),
|
|
700
|
+
// The api-key `user_id` the fire runs AS.
|
|
701
|
+
execution_key: z.string().min(1),
|
|
690
702
|
tool_kwargs: z.record(z.string(), z.unknown()).default({}),
|
|
691
703
|
condition: z.string().nullable().default(null),
|
|
692
704
|
condition_id: z.string().nullable().default(null),
|
|
@@ -711,7 +723,10 @@ export const hookList = z.object({
|
|
|
711
723
|
z.object({ verifier: z.string(), config: z.record(z.string(), z.unknown()) }),
|
|
712
724
|
)
|
|
713
725
|
.default({}),
|
|
726
|
+
// Per-topic fire door (topic → door). Tolerant of an older response omitting it.
|
|
727
|
+
trigger_auth: z.record(z.string(), triggerAuth).default({}),
|
|
714
728
|
});
|
|
729
|
+
export type HookList = z.infer<typeof hookList>;
|
|
715
730
|
|
|
716
731
|
export const hookRegistered = z.object({
|
|
717
732
|
registered: z.boolean(),
|
|
@@ -734,6 +749,58 @@ export const topicVerifierSet = z.object({ topic: z.string(), verifier: z.string
|
|
|
734
749
|
/** `DELETE /api/hooks/topics/{topic}/verifier` — the unbound topic. */
|
|
735
750
|
export const topicVerifierRemoved = z.object({ removed: z.boolean(), topic: z.string() });
|
|
736
751
|
|
|
752
|
+
// -- trigger links -----------------------------------------------------------
|
|
753
|
+
// A trigger link is a minted, token-bearing PUBLIC URL (`GET|POST /trigger/{token}`)
|
|
754
|
+
// that resolves to a hook TOPIC and fires it. Studio mints, lists, and revokes them
|
|
755
|
+
// on the hooks page; the QR encodes the composed absolute URL.
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* `POST /api/hooks/trigger-links` — a freshly minted trigger link. `trigger_path`
|
|
759
|
+
* is a relative path (`/trigger/<token>`, never an absolute URL — Studio composes
|
|
760
|
+
* the API origin); `token` is the raw token returned exactly ONCE, in this response
|
|
761
|
+
* (nothing else stores or lists it — a listed link's QR is unrecoverable by
|
|
762
|
+
* design). `expires_at` is an ISO-8601 instant, or `null` for a permanent link.
|
|
763
|
+
*/
|
|
764
|
+
export const triggerLinkCreated = z.object({
|
|
765
|
+
name: z.string(),
|
|
766
|
+
trigger_path: z.string(),
|
|
767
|
+
token: z.string(),
|
|
768
|
+
topic: z.string(),
|
|
769
|
+
expires_at: z.string().nullable(),
|
|
770
|
+
});
|
|
771
|
+
export type TriggerLinkCreated = z.infer<typeof triggerLinkCreated>;
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* One listed trigger-link record (`GET /api/hooks/trigger-links` item). `tool_kwargs`
|
|
775
|
+
* is the per-link params merged last into every fire (`null` or `{}` = none);
|
|
776
|
+
* `created_by` is the ambient creator identity (`null` on an identity-less,
|
|
777
|
+
* gate-off deployment); `token_hash_prefix` is a short hash prefix for log
|
|
778
|
+
* correlation — never a raw token (none is stored).
|
|
779
|
+
*/
|
|
780
|
+
export const triggerLinkRecord = z.object({
|
|
781
|
+
name: z.string(),
|
|
782
|
+
topic: z.string(),
|
|
783
|
+
execution_key: z.string().min(1),
|
|
784
|
+
trigger_auth: triggerAuth,
|
|
785
|
+
tool_kwargs: z.record(z.string(), z.unknown()).nullable(),
|
|
786
|
+
created_by: z.string().nullable(),
|
|
787
|
+
created_at: z.string(),
|
|
788
|
+
expires_at: z.string().nullable(),
|
|
789
|
+
token_hash_prefix: z.string(),
|
|
790
|
+
});
|
|
791
|
+
export type TriggerLinkRecord = z.infer<typeof triggerLinkRecord>;
|
|
792
|
+
|
|
793
|
+
/** `GET /api/hooks/trigger-links` — every live trigger-link record. */
|
|
794
|
+
export const triggerLinkList = z.object({
|
|
795
|
+
items: z.array(triggerLinkRecord),
|
|
796
|
+
total: z.number(),
|
|
797
|
+
});
|
|
798
|
+
export type TriggerLinkList = z.infer<typeof triggerLinkList>;
|
|
799
|
+
|
|
800
|
+
/** `DELETE /api/hooks/trigger-links/{name}` — the revoked link. */
|
|
801
|
+
export const triggerLinkDeleted = z.object({ removed: z.boolean(), name: z.string() });
|
|
802
|
+
export type TriggerLinkDeleted = z.infer<typeof triggerLinkDeleted>;
|
|
803
|
+
|
|
737
804
|
// -- settings schema ---------------------------------------------------------
|
|
738
805
|
// The settings form is auto-rendered from a declarative group/field description
|
|
739
806
|
// the skeleton emits from its typed settings models.
|
|
@@ -771,20 +838,113 @@ export const addUrlToScopeResult = z.object({ scope_id: z.string(), url: z.strin
|
|
|
771
838
|
export const removeUrlFromScopeResult = z.object({ url: z.string() });
|
|
772
839
|
export const removeScopeResult = z.object({ scope_id: z.string(), deleted_keys: z.number() });
|
|
773
840
|
|
|
841
|
+
/**
|
|
842
|
+
* A route's authorization ACTION class, joined onto the catalog from the route
|
|
843
|
+
* registry. `read`/`write` routes are GRANTABLE — a role's per-tag level opens
|
|
844
|
+
* them; `fenced`/`secret` routes are ADMIN-ONLY and never grantable (no per-tag
|
|
845
|
+
* level reaches them). `null` marks a path the registry carries no metadata for
|
|
846
|
+
* (an unregistered/ungated path — no feature tags, no action).
|
|
847
|
+
*/
|
|
848
|
+
export const routeAction = z.enum(['read', 'write', 'fenced', 'secret']);
|
|
849
|
+
export type RouteAction = z.infer<typeof routeAction>;
|
|
850
|
+
|
|
774
851
|
/**
|
|
775
852
|
* One row of the route catalog (`GET /api/auth/routes`) — a plain HTTP route with
|
|
776
|
-
* its current mapping. `mapped` is the
|
|
777
|
-
* the path is unmapped (the mapper's
|
|
778
|
-
* route's method set with `HEAD`
|
|
853
|
+
* its current mapping, JOINED with its route-registry metadata. `mapped` is the
|
|
854
|
+
* scope id, the public marker, or `null` when the path is unmapped (the mapper's
|
|
855
|
+
* "unassigned" bucket source). `methods` is the route's method set with `HEAD`
|
|
856
|
+
* removed. `tags` are the route's feature-group labels, `summary` its one-line
|
|
857
|
+
* description, and `action` its authorization class — the join fields the Roles
|
|
858
|
+
* grant editor reads to derive grantable feature groups and mark the admin-only
|
|
859
|
+
* (fenced/secret) routes it must never offer a grant for. An unregistered path
|
|
860
|
+
* carries `tags: []`, `summary: ''`, `action: null`.
|
|
779
861
|
*/
|
|
780
862
|
export const authRoute = z.object({
|
|
781
863
|
path: z.string(),
|
|
782
864
|
methods: z.array(z.string()),
|
|
783
865
|
mapped: z.string().nullable(),
|
|
866
|
+
tags: z.array(z.string()),
|
|
867
|
+
summary: z.string(),
|
|
868
|
+
action: routeAction.nullable(),
|
|
784
869
|
});
|
|
785
870
|
export type AuthRoute = z.infer<typeof authRoute>;
|
|
786
871
|
export const authRoutes = z.array(authRoute);
|
|
787
872
|
|
|
873
|
+
// -- auth: roles (editable per-tag RBAC) -------------------------------------
|
|
874
|
+
|
|
875
|
+
/** A role's per-tag ACCESS LEVEL: `none` (no access), `read`, or `write`. */
|
|
876
|
+
export const grantLevel = z.enum(['none', 'read', 'write']);
|
|
877
|
+
export type GrantLevel = z.infer<typeof grantLevel>;
|
|
878
|
+
|
|
879
|
+
/** A role's editable grant map: feature-group TAG name → the role's access level. */
|
|
880
|
+
export const roleGrants = z.record(z.string(), grantLevel);
|
|
881
|
+
export type RoleGrants = z.infer<typeof roleGrants>;
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* A role body (`GET /api/auth/roles`, and the write routes' echo). Layer 1 is the
|
|
885
|
+
* read-only base-tier ceiling — the seeded jq `condition` plus `base_tier`
|
|
886
|
+
* (`editor`/`viewer`, or `null` for the reserved `allow_all` admin). Layer 2 is the
|
|
887
|
+
* editable per-tag `grants` map. `allow_all` (admin) carries an empty `grants` map
|
|
888
|
+
* and reaches everything, un-lockable. `condition`/`condition_id`/`condition_kwargs`
|
|
889
|
+
* are the base-tier jq — READ-ONLY here (no raw-jq authoring surface).
|
|
890
|
+
*/
|
|
891
|
+
export const roleBody = z.object({
|
|
892
|
+
name: z.string(),
|
|
893
|
+
description: z.string(),
|
|
894
|
+
scopes: z.array(z.string()),
|
|
895
|
+
condition: z.string().nullable(),
|
|
896
|
+
condition_id: z.string().nullable(),
|
|
897
|
+
condition_kwargs: jsonValue.nullable(),
|
|
898
|
+
base_tier: z.string().nullable(),
|
|
899
|
+
allow_all: z.boolean(),
|
|
900
|
+
grants: roleGrants,
|
|
901
|
+
});
|
|
902
|
+
export type RoleBody = z.infer<typeof roleBody>;
|
|
903
|
+
export const roleList = z.array(roleBody);
|
|
904
|
+
|
|
905
|
+
/** `DELETE /api/auth/roles/{name}` — the deleted role's name. */
|
|
906
|
+
export const roleDeleted = z.object({ name: z.string(), deleted: z.boolean() });
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* One immutable role-version row (`GET /api/auth/roles/{name}/versions` →
|
|
910
|
+
* `versions`). `body` is the role body at that version; `is_current` flags the
|
|
911
|
+
* active pointer. Mirrors the generic versioned-store row shape.
|
|
912
|
+
*/
|
|
913
|
+
export const roleVersion = z.object({
|
|
914
|
+
version: z.number(),
|
|
915
|
+
body: roleBody,
|
|
916
|
+
tags: z.array(z.string()),
|
|
917
|
+
created_at: z.string(),
|
|
918
|
+
is_current: z.boolean(),
|
|
919
|
+
});
|
|
920
|
+
export type RoleVersion = z.infer<typeof roleVersion>;
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* One who/action/before→after audit row (`GET /api/auth/roles/{name}/versions` →
|
|
924
|
+
* `audit`). Its `body` carries the mutation kind, the actor, and the before/after
|
|
925
|
+
* role bodies (both `null` for a create's `before` / a delete's `after`).
|
|
926
|
+
*/
|
|
927
|
+
export const roleAuditEvent = z.object({
|
|
928
|
+
version: z.number(),
|
|
929
|
+
body: z.object({
|
|
930
|
+
action: z.string(),
|
|
931
|
+
actor: z.string().nullable(),
|
|
932
|
+
before: jsonValue.nullable(),
|
|
933
|
+
after: jsonValue.nullable(),
|
|
934
|
+
}),
|
|
935
|
+
tags: z.array(z.string()),
|
|
936
|
+
created_at: z.string(),
|
|
937
|
+
is_current: z.boolean(),
|
|
938
|
+
});
|
|
939
|
+
export type RoleAuditEvent = z.infer<typeof roleAuditEvent>;
|
|
940
|
+
|
|
941
|
+
/** `GET /api/auth/roles/{name}/versions` — the append-only history + audit trail. */
|
|
942
|
+
export const roleVersions = z.object({
|
|
943
|
+
versions: z.array(roleVersion),
|
|
944
|
+
audit: z.array(roleAuditEvent),
|
|
945
|
+
});
|
|
946
|
+
export type RoleVersions = z.infer<typeof roleVersions>;
|
|
947
|
+
|
|
788
948
|
/** `GET /api/auth/public-routes` — the sorted urls pinned to the public marker. */
|
|
789
949
|
export const publicRoutes = z.array(z.string());
|
|
790
950
|
|
|
@@ -793,9 +953,12 @@ export const pinPublicResult = z.object({ url: z.string() });
|
|
|
793
953
|
/** `DELETE /api/auth/public-routes` — the unpinned url (404 when not pinned). */
|
|
794
954
|
export const unpinPublicResult = z.object({ url: z.string() });
|
|
795
955
|
|
|
956
|
+
/** The per-key payloads (`GET /api/auth/tokens-payload`). The stable per-mint
|
|
957
|
+
* `key_fingerprint` is nested under `policy_data`; the picker reads it there. */
|
|
796
958
|
export const tokensPayload = z.array(
|
|
797
959
|
z.object({
|
|
798
|
-
|
|
960
|
+
// The value an `execution_key` binding names.
|
|
961
|
+
user_id: z.string().min(1),
|
|
799
962
|
description: z.string(),
|
|
800
963
|
scopes: z.array(z.string()),
|
|
801
964
|
policy_data: jsonValue,
|
|
@@ -806,8 +969,11 @@ export const tokensPayload = z.array(
|
|
|
806
969
|
);
|
|
807
970
|
export type TokensPayload = z.infer<typeof tokensPayload>;
|
|
808
971
|
|
|
809
|
-
/**
|
|
810
|
-
|
|
972
|
+
/** Mint reply `{ api_key, key_fingerprint }` narrowed to the raw `sk-…` key,
|
|
973
|
+
* returned once at creation and never again. */
|
|
974
|
+
export const createdApiKey = z
|
|
975
|
+
.object({ api_key: z.string(), key_fingerprint: z.string() })
|
|
976
|
+
.transform((minted) => minted.api_key);
|
|
811
977
|
export const editApiKeyResult = z.object({ user_id: z.string(), updated: z.boolean() });
|
|
812
978
|
export const revokeApiKeyResult = z.object({ user_id: z.string(), revoked: z.boolean() });
|
|
813
979
|
|