@maravilla-labs/types 0.5.0 → 0.6.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/index.ts CHANGED
@@ -176,12 +176,33 @@ export interface DbFindOptions {
176
176
  vector?: VectorQuery;
177
177
  }
178
178
 
179
+ /**
180
+ * A document as it is returned by the runtime. Every row the platform
181
+ * stores carries a string `id` (mirrored as `_id`) injected at insert
182
+ * time, plus optional server-managed timestamps. `find`/`findOne` return
183
+ * this shape; the generic `T` is your own document fields.
184
+ *
185
+ * This is a **type-level** contract — the runtime already injects/returns
186
+ * `id`+`_id` on both the dev (SQLite) and production (Mongo) paths, so no
187
+ * normalization happens here.
188
+ */
189
+ export type DbDocument<T = Record<string, unknown>> = T & {
190
+ /** Stable string row id, injected by the runtime on insert. */
191
+ id: string;
192
+ /** Mirror of `id` for MongoDB-style call sites. Always equals `id`. */
193
+ _id: string;
194
+ /** Server-managed creation timestamp, when present. */
195
+ _created_at?: string;
196
+ /** Server-managed update timestamp, when present. */
197
+ _updated_at?: string;
198
+ };
199
+
179
200
  /**
180
201
  * Database collection interface
181
202
  */
182
203
  export interface DbCollection {
183
- find(filter?: Record<string, any>, options?: DbFindOptions): Promise<any[]>;
184
- findOne(filter?: Record<string, any>): Promise<any | null>;
204
+ find<T = Record<string, unknown>>(filter?: Record<string, any>, options?: DbFindOptions): Promise<DbDocument<T>[]>;
205
+ findOne<T = Record<string, unknown>>(filter?: Record<string, any>): Promise<DbDocument<T> | null>;
185
206
  insertOne(document: Record<string, any>): Promise<string>;
186
207
  updateOne(filter: Record<string, any>, update: Record<string, any>): Promise<void>;
187
208
  deleteOne(filter: Record<string, any>): Promise<void>;
@@ -317,7 +338,378 @@ export interface Storage {
317
338
  getMetadata(key: string): Promise<StorageMetadata>;
318
339
  }
319
340
 
320
- // Stewardship Types
341
+ // ════════════════════════════════════════════════════════════════════
342
+ // Auth, Stewardship, Resources, Circles, Groups, Relations
343
+ // ════════════════════════════════════════════════════════════════════
344
+ //
345
+ // These are the CANONICAL definitions for the entire auth/policy/relations
346
+ // surface. `@maravilla-labs/platform` re-exports every symbol below from
347
+ // its own `types.ts`, and `RemoteAuthService implements AuthService`. Keep
348
+ // this the single source of truth — do not fork divergent copies in the
349
+ // platform package.
350
+
351
+ /**
352
+ * Authenticated user record from the platform auth service.
353
+ */
354
+ export interface AuthUser {
355
+ /** Unique user ID (prefixed with "usr_") */
356
+ id: string;
357
+ /** User's email address */
358
+ email: string;
359
+ /** Whether the email has been verified */
360
+ email_verified: boolean;
361
+ /** Account status */
362
+ status: 'active' | 'suspended' | 'deactivated';
363
+ /** Authentication provider ("email", "google", "github", "managed", etc.) */
364
+ provider: string;
365
+ /** Group IDs the user belongs to */
366
+ groups: string[];
367
+ /**
368
+ * Caller-supplied external identifier used for idempotent
369
+ * managed-user creation (FR-3). `null`/absent for normal accounts.
370
+ */
371
+ external_id?: string | null;
372
+ /** Unix timestamp when the user was created */
373
+ created_at: number;
374
+ /** Unix timestamp when the user was last updated */
375
+ updated_at: number;
376
+ /** Unix timestamp of last login (if any) */
377
+ last_login_at?: number;
378
+ }
379
+
380
+ /**
381
+ * Snapshot of whoever is currently bound to the request as the caller.
382
+ * This is exactly what per-resource policies see as `auth.*`.
383
+ */
384
+ export interface AuthCaller {
385
+ /** Caller's user id, or `""` if anonymous */
386
+ user_id: string;
387
+ /** Caller's email, or `""` if anonymous */
388
+ email: string;
389
+ /** Admin flag from the session */
390
+ is_admin: boolean;
391
+ /** Role names (project-scoped) */
392
+ roles: string[];
393
+ /** `true` when no identity is bound to this request */
394
+ is_anonymous: boolean;
395
+ }
396
+
397
+ /**
398
+ * Session returned after successful login or token refresh.
399
+ */
400
+ export interface AuthSession {
401
+ /** Short-lived JWT access token (default 15 min) */
402
+ access_token: string;
403
+ /** Single-use opaque refresh token (default 30 days) */
404
+ refresh_token: string;
405
+ /** Access token lifetime in seconds */
406
+ expires_in: number;
407
+ /** The authenticated user */
408
+ user: AuthUser;
409
+ }
410
+
411
+ /**
412
+ * Custom registration field defined in project auth settings.
413
+ */
414
+ export interface AuthField {
415
+ /** Field key (used as form field name) */
416
+ key: string;
417
+ /** Display label */
418
+ label: string;
419
+ /** Field type: text, email, phone, date, number, select, boolean, url, textarea */
420
+ field_type: string;
421
+ /** Whether the field is required */
422
+ required: boolean;
423
+ /** Whether the field appears on the registration form */
424
+ show_on_register: boolean;
425
+ }
426
+
427
+ /**
428
+ * Options for registering a new user.
429
+ */
430
+ export interface RegisterOptions {
431
+ /** User's email address */
432
+ email: string;
433
+ /** Password (minimum 8 characters) */
434
+ password: string;
435
+ /** Optional profile data (custom fields) */
436
+ profile?: Record<string, any>;
437
+ /**
438
+ * Caller-supplied external id. When set, registration is idempotent on
439
+ * `(tenant, external_id)` — re-registering with the same key returns the
440
+ * existing user instead of erroring (FR-3).
441
+ */
442
+ external_id?: string;
443
+ }
444
+
445
+ /**
446
+ * Options for logging in.
447
+ */
448
+ export interface LoginOptions {
449
+ /** User's email address */
450
+ email: string;
451
+ /** User's password */
452
+ password: string;
453
+ }
454
+
455
+ /**
456
+ * Options for creating a managed (no-login) user — e.g. an imported
457
+ * contact or service-owned record that authenticates out-of-band (FR-2).
458
+ * Created with no password; sessions are only minted when the account is
459
+ * later activated.
460
+ */
461
+ export interface CreateManagedUserOptions {
462
+ /** Optional email. A synthetic no-login address is generated when omitted. */
463
+ email?: string;
464
+ /** Optional profile data. */
465
+ profile?: Record<string, any>;
466
+ /** Optional external id for idempotent create-by-key (FR-3). */
467
+ external_id?: string;
468
+ /** Optional group ids/slugs to add the user to on creation. */
469
+ groups?: string[];
470
+ }
471
+
472
+ /**
473
+ * Filter options for listing users.
474
+ */
475
+ export interface UserListFilter {
476
+ /** Max results per page (default 50) */
477
+ limit?: number;
478
+ /** Number of results to skip */
479
+ offset?: number;
480
+ /** Filter by account status */
481
+ status?: 'active' | 'suspended' | 'deactivated';
482
+ /** Filter by email (partial match) */
483
+ email_contains?: string;
484
+ /** Filter by group ID */
485
+ group_id?: string;
486
+ }
487
+
488
+ /**
489
+ * Paginated user list response.
490
+ */
491
+ export interface UserListResponse {
492
+ /** Users in this page */
493
+ users: AuthUser[];
494
+ /** Total number of matching users */
495
+ total: number;
496
+ /** Page size */
497
+ limit: number;
498
+ /** Offset */
499
+ offset: number;
500
+ }
501
+
502
+ /**
503
+ * Options for updating a user.
504
+ */
505
+ export interface UpdateUserOptions {
506
+ /** New email address */
507
+ email?: string;
508
+ /** New status */
509
+ status?: 'active' | 'suspended' | 'deactivated';
510
+ /** Profile data to merge */
511
+ profile?: Record<string, any>;
512
+ }
513
+
514
+ // ── Groups (RBAC) ──
515
+
516
+ export interface AuthGroup {
517
+ id: string;
518
+ name: string;
519
+ description: string | null;
520
+ permissions: string[];
521
+ member_count: number;
522
+ created_at: number;
523
+ updated_at: number;
524
+ }
525
+
526
+ export interface CreateGroupOptions {
527
+ name: string;
528
+ description?: string;
529
+ permissions?: string[];
530
+ }
531
+
532
+ export interface UpdateGroupOptions {
533
+ name?: string;
534
+ description?: string;
535
+ permissions?: string[];
536
+ }
537
+
538
+ export interface GroupPermission {
539
+ resource_name: string;
540
+ actions: string[];
541
+ }
542
+
543
+ // ── Circles ──
544
+
545
+ export interface AuthCircle {
546
+ id: string;
547
+ name: string;
548
+ metadata: Record<string, any> | null;
549
+ member_count: number;
550
+ created_at: number;
551
+ updated_at: number;
552
+ }
553
+
554
+ export interface CreateCircleOptions {
555
+ name: string;
556
+ metadata?: Record<string, any>;
557
+ }
558
+
559
+ export interface UpdateCircleOptions {
560
+ name?: string;
561
+ metadata?: Record<string, any>;
562
+ }
563
+
564
+ export interface AddCircleMemberOptions {
565
+ user_id: string;
566
+ relationship: string;
567
+ is_primary_contact?: boolean;
568
+ }
569
+
570
+ /** Circle membership entry */
571
+ export interface CircleMembership {
572
+ user_id: string;
573
+ email: string;
574
+ relationship: string;
575
+ is_primary_contact: boolean;
576
+ joined_at: number;
577
+ }
578
+
579
+ // ── Resources ──
580
+
581
+ export type ResourceServiceType =
582
+ | 'kv'
583
+ | 'database'
584
+ | 'realtime'
585
+ | 'media'
586
+ | 'vector'
587
+ | 'storage'
588
+ | 'queue'
589
+ | 'push'
590
+ | 'workflow'
591
+ | 'transforms';
592
+
593
+ /** A platform resource definition. */
594
+ export interface Resource {
595
+ id: string;
596
+ resource_name: string;
597
+ title: string;
598
+ description: string | null;
599
+ actions: string[];
600
+ policy: string | null;
601
+ service_type: ResourceServiceType | null;
602
+ read_filter: string | null;
603
+ created_at: number;
604
+ updated_at: number;
605
+ }
606
+
607
+ export interface CreateResourceOptions {
608
+ resource_name: string;
609
+ title: string;
610
+ description?: string;
611
+ actions?: string[];
612
+ policy?: string;
613
+ service_type?: ResourceServiceType;
614
+ read_filter?: string;
615
+ }
616
+
617
+ export interface UpdateResourceOptions {
618
+ title?: string;
619
+ description?: string;
620
+ actions?: string[];
621
+ policy?: string;
622
+ service_type?: ResourceServiceType;
623
+ read_filter?: string;
624
+ }
625
+
626
+ // ── Relations (typed edges) ──
627
+
628
+ export interface RelationType {
629
+ id: string;
630
+ relation_name: string;
631
+ title: string;
632
+ description: string | null;
633
+ category: string;
634
+ icon: string | null;
635
+ color: string | null;
636
+ inverse_relation_id: string | null;
637
+ implies_stewardship: boolean;
638
+ requires_minor: boolean;
639
+ bidirectional: boolean;
640
+ is_system: boolean;
641
+ created_at: number;
642
+ updated_at: number;
643
+ }
644
+
645
+ export interface CreateRelationTypeOptions {
646
+ relation_name: string;
647
+ title: string;
648
+ description?: string;
649
+ category?: string;
650
+ icon?: string;
651
+ color?: string;
652
+ inverse_relation_id?: string;
653
+ implies_stewardship?: boolean;
654
+ requires_minor?: boolean;
655
+ bidirectional?: boolean;
656
+ is_system?: boolean;
657
+ }
658
+
659
+ export interface UpdateRelationTypeOptions {
660
+ title?: string;
661
+ description?: string;
662
+ category?: string;
663
+ icon?: string;
664
+ color?: string;
665
+ inverse_relation_id?: string;
666
+ implies_stewardship?: boolean;
667
+ requires_minor?: boolean;
668
+ bidirectional?: boolean;
669
+ }
670
+
671
+ /** A single typed relation edge between two users (FR-1). */
672
+ export interface Relation {
673
+ id: string;
674
+ from_user_id: string;
675
+ to_user_id: string;
676
+ /** The relation type's name (e.g. `STEWARDS`). */
677
+ relation_type: string;
678
+ /** The relation type's id (`rlt_…`). */
679
+ relation_type_id: string;
680
+ metadata?: Record<string, any> | null;
681
+ created_at: number;
682
+ }
683
+
684
+ /** Options for adding a relation edge. */
685
+ export interface AddRelationOptions {
686
+ from_user_id: string;
687
+ to_user_id: string;
688
+ /** Either a relation-type id (`rlt_…`) or a relation name. */
689
+ relation_type: string;
690
+ metadata?: Record<string, any>;
691
+ }
692
+
693
+ /** Direction filter when listing a user's relations. */
694
+ export type RelationListDirection = 'outgoing' | 'incoming' | 'both';
695
+
696
+ /** Options for listing relation edges touching a user. */
697
+ export interface ListRelationsOptions {
698
+ user_id: string;
699
+ direction?: RelationListDirection;
700
+ }
701
+
702
+ // ── Auth config (extended) ──
703
+
704
+ export interface AuthConfig {
705
+ fields: AuthField[];
706
+ oauth_providers: any[];
707
+ branding: Record<string, any>;
708
+ password_policy: Record<string, any>;
709
+ session_config: Record<string, any>;
710
+ }
711
+
712
+ // ── Stewardship ──
321
713
 
322
714
  /** Delegation mode for stewardship overrides */
323
715
  export type DelegationMode = 'full' | 'scoped';
@@ -340,19 +732,19 @@ export interface StewardshipOverride {
340
732
  ward_id: string;
341
733
  delegation_mode: DelegationMode;
342
734
  scoped_permissions: ScopedPermission[];
343
- valid_from?: number;
344
- valid_until?: number;
735
+ valid_from: number | null;
736
+ valid_until: number | null;
345
737
  status: StewardshipStatus;
346
- reason?: string;
738
+ reason: string | null;
347
739
  source: string;
348
- source_circle_id?: string;
349
- source_relation_type_id?: string;
740
+ source_circle_id: string | null;
741
+ source_relation_type_id: string | null;
350
742
  created_at: number;
351
743
  updated_at: number;
352
744
  }
353
745
 
354
746
  /** Options for creating a stewardship override */
355
- export interface CreateStewardshipOverrideRequest {
747
+ export interface CreateStewardshipOverrideOptions {
356
748
  steward_id: string;
357
749
  ward_id: string;
358
750
  delegation_mode?: DelegationMode;
@@ -386,67 +778,261 @@ export interface StewardshipAuditEntry {
386
778
  performed_by: string;
387
779
  on_behalf_of: string;
388
780
  action: string;
389
- resource?: string;
390
- details?: Record<string, any>;
781
+ resource: string | null;
782
+ details: Record<string, any> | null;
391
783
  created_at: number;
392
784
  }
393
785
 
394
- /** Options for listing audit log entries */
395
- export interface AuditListOptions {
396
- limit?: number;
397
- offset?: number;
398
- }
399
-
400
- /** Stewardship service interface */
401
- export interface Stewardship {
786
+ /**
787
+ * Sub-namespace exposed at `platform.auth.stewardship` mirroring the
788
+ * runtime's `globalThis.platform.auth.stewardship.*` surface.
789
+ */
790
+ export interface AuthStewardshipApi {
402
791
  resolve(userId: string): Promise<StewardshipResolution>;
403
- createOverride(options: CreateStewardshipOverrideRequest): Promise<StewardshipOverride>;
792
+ createOverride(opts: CreateStewardshipOverrideOptions): Promise<StewardshipOverride>;
404
793
  revoke(id: string): Promise<void>;
405
794
  checkPermission(stewardId: string, wardId: string, resource: string, action: string): Promise<boolean>;
406
795
  createActAs(stewardId: string, wardId: string): Promise<ActAsContext>;
407
- listAudit(userId: string, options?: AuditListOptions): Promise<StewardshipAuditEntry[]>;
796
+ listAudit(userId: string, options?: { limit?: number; offset?: number }): Promise<StewardshipAuditEntry[]>;
408
797
  }
409
798
 
410
- // Resource Types
799
+ // ── Authorization explain / batch (FR-7) ──
411
800
 
412
- /** A platform resource definition */
413
- export interface Resource {
414
- id: string;
415
- resource_name: string;
416
- title: string;
417
- description?: string;
418
- actions: string[];
419
- created_at: number;
420
- updated_at: number;
801
+ /** Result of an `explain()` policy check. */
802
+ export interface PolicyExplain {
803
+ allowed: boolean;
804
+ reason?: string;
805
+ /** The specific clause that caused a denial, when the engine can isolate it. */
806
+ failedClause?: string;
421
807
  }
422
808
 
423
- /** Options for creating a resource */
424
- export interface CreateResourceRequest {
425
- resource_name: string;
426
- title: string;
427
- description?: string;
428
- actions?: string[];
809
+ /** A single authorization check for batched `canMany()`. */
810
+ export interface CanCheck {
811
+ action: string;
812
+ resourceId: string;
813
+ node?: Record<string, unknown> | null;
429
814
  }
430
815
 
431
- // Circle Types
816
+ /**
817
+ * Auth service for end-user authentication and user management.
818
+ * Implemented by the in-runtime native bridge and by the dev-server
819
+ * remote client (`RemoteAuthService`).
820
+ */
821
+ export interface AuthService {
822
+ /**
823
+ * Register a new user with email and password.
824
+ * @returns The created user (not yet email-verified)
825
+ */
826
+ register(options: RegisterOptions): Promise<AuthUser>;
432
827
 
433
- /** Circle membership entry */
434
- export interface CircleMembership {
435
- user_id: string;
436
- email: string;
437
- relationship: string;
438
- is_primary_contact: boolean;
439
- joined_at: number;
440
- }
828
+ /**
829
+ * Authenticate a user and create a session.
830
+ * @returns Session with access token, refresh token, and user info
831
+ */
832
+ login(options: LoginOptions): Promise<AuthSession>;
441
833
 
442
- /** A circle */
443
- export interface Circle {
444
- id: string;
445
- name: string;
446
- metadata?: Record<string, any>;
447
- member_count: number;
448
- created_at: number;
449
- updated_at: number;
834
+ /**
835
+ * Validate an access token and return the authenticated user.
836
+ * @throws If the token is invalid or expired
837
+ */
838
+ validate(accessToken: string): Promise<AuthUser>;
839
+
840
+ /**
841
+ * Refresh a session using a refresh token (single-use).
842
+ * @returns New session with fresh access and refresh tokens
843
+ */
844
+ refresh(refreshToken: string): Promise<AuthSession>;
845
+
846
+ /** Revoke a specific session. */
847
+ logout(sessionId: string): Promise<void>;
848
+
849
+ /**
850
+ * Get a user by ID.
851
+ * @returns The user, or null if not found
852
+ */
853
+ getUser(userId: string): Promise<AuthUser | null>;
854
+
855
+ /** List users with optional filtering and pagination. */
856
+ listUsers(filter?: UserListFilter): Promise<UserListResponse>;
857
+
858
+ /** Update a user's email, status, or profile data. */
859
+ updateUser(userId: string, update: UpdateUserOptions): Promise<AuthUser>;
860
+
861
+ /** Delete a user and all their sessions. */
862
+ deleteUser(userId: string): Promise<void>;
863
+
864
+ /**
865
+ * Create a managed (no-login) user (FR-2). Idempotent on `external_id`
866
+ * when supplied (FR-3).
867
+ */
868
+ createManagedUser(options: CreateManagedUserOptions): Promise<AuthUser>;
869
+
870
+ /**
871
+ * Create an email verification token.
872
+ * @returns The verification token (caller decides how to deliver it)
873
+ */
874
+ sendVerification(userId: string): Promise<{ token: string }>;
875
+
876
+ /** Verify an email address using a verification token. */
877
+ verifyEmail(token: string): Promise<void>;
878
+
879
+ /**
880
+ * Create a password reset token for an email address.
881
+ * @returns The reset token (caller decides how to deliver it)
882
+ */
883
+ sendPasswordReset(email: string): Promise<{ token: string }>;
884
+
885
+ /** Reset a password using a reset token. */
886
+ resetPassword(token: string, newPassword: string): Promise<void>;
887
+
888
+ /** Change a user's password (requires old password). */
889
+ changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void>;
890
+
891
+ /** Get the configured registration fields for this project. */
892
+ getFieldConfig(): Promise<{ fields: AuthField[] }>;
893
+
894
+ /**
895
+ * Start an OAuth flow by generating an authorization URL.
896
+ * @param provider - "google", "github", "okta", or "custom_oidc"
897
+ */
898
+ getOAuthUrl(provider: string, options?: { redirectUri?: string }): Promise<{ auth_url: string; state: string }>;
899
+
900
+ /** Complete an OAuth flow by exchanging the authorization code. */
901
+ handleOAuthCallback(provider: string, params: { code: string; state: string }): Promise<
902
+ | AuthSession
903
+ | { type: 'LinkRequired'; email: string; provider: string; provider_id: string; existing_user_id: string }
904
+ >;
905
+
906
+ /**
907
+ * Middleware helper that validates auth and injects `request.user`.
908
+ * Returns 401 JSON response if no valid token is found.
909
+ */
910
+ withAuth<T extends (request: Request & { user: AuthUser }) => Promise<Response>>(
911
+ handler: T
912
+ ): (request: Request) => Promise<Response>;
913
+
914
+ // ── Groups (RBAC) ──
915
+
916
+ createGroup(options: CreateGroupOptions): Promise<AuthGroup>;
917
+ listGroups(): Promise<AuthGroup[]>;
918
+ /** @param groupId - the group id **or slug** (resolved server-side). */
919
+ getGroup(groupId: string): Promise<AuthGroup | null>;
920
+ /**
921
+ * Look up a group by its declarative name (the same name used in
922
+ * `maravilla.config.ts`'s `groups: [...]` block). Returns null if the
923
+ * auth-settings reconciler hasn't created it yet.
924
+ */
925
+ getGroupByName(name: string): Promise<AuthGroup | null>;
926
+ /** @param groupId - the group id **or slug** (resolved server-side). */
927
+ updateGroup(groupId: string, options: UpdateGroupOptions): Promise<AuthGroup>;
928
+ /** @param groupId - the group id **or slug** (resolved server-side). */
929
+ deleteGroup(groupId: string): Promise<void>;
930
+ /** @param groupId - the group id **or slug** (resolved server-side). */
931
+ addUserToGroup(userId: string, groupId: string): Promise<void>;
932
+ /** @param groupId - the group id **or slug** (resolved server-side). */
933
+ removeUserFromGroup(userId: string, groupId: string): Promise<void>;
934
+ getUserGroups(userId: string): Promise<AuthGroup[]>;
935
+ /** @param groupId - the group id **or slug** (resolved server-side). */
936
+ getGroupMembers(groupId: string): Promise<AuthUser[]>;
937
+ /** @param groupId - the group id **or slug** (resolved server-side). */
938
+ getGroupPermissions(groupId: string): Promise<GroupPermission[]>;
939
+ /** @param groupId - the group id **or slug** (resolved server-side). */
940
+ setGroupPermissions(groupId: string, permissions: GroupPermission[]): Promise<void>;
941
+
942
+ // ── Circles ──
943
+
944
+ createCircle(options: CreateCircleOptions): Promise<AuthCircle>;
945
+ listCircles(): Promise<AuthCircle[]>;
946
+ /** @param circleId - the circle id **or slug** (resolved server-side). */
947
+ getCircle(circleId: string): Promise<AuthCircle | null>;
948
+ /** @param circleId - the circle id **or slug** (resolved server-side). */
949
+ updateCircle(circleId: string, options: UpdateCircleOptions): Promise<AuthCircle>;
950
+ /** @param circleId - the circle id **or slug** (resolved server-side). */
951
+ deleteCircle(circleId: string): Promise<void>;
952
+ /** @param circleId - the circle id **or slug** (resolved server-side). */
953
+ addCircleMember(circleId: string, options: AddCircleMemberOptions): Promise<void>;
954
+ /** @param circleId - the circle id **or slug** (resolved server-side). */
955
+ removeCircleMember(circleId: string, userId: string): Promise<void>;
956
+ /** @param circleId - the circle id **or slug** (resolved server-side). */
957
+ getCircleMembers(circleId: string): Promise<CircleMembership[]>;
958
+ getUserCircles(userId: string): Promise<AuthCircle[]>;
959
+
960
+ // ── Resources ──
961
+
962
+ createResource(options: CreateResourceOptions): Promise<Resource>;
963
+ listResources(): Promise<Resource[]>;
964
+ updateResource(resourceId: string, options: UpdateResourceOptions): Promise<Resource>;
965
+ deleteResource(resourceId: string): Promise<void>;
966
+
967
+ // ── Relation types ──
968
+
969
+ createRelationType(options: CreateRelationTypeOptions): Promise<RelationType>;
970
+ listRelationTypes(): Promise<RelationType[]>;
971
+ /** @param id - the relation-type id **or slug** (resolved server-side). */
972
+ updateRelationType(id: string, options: UpdateRelationTypeOptions): Promise<RelationType>;
973
+ /** @param id - the relation-type id **or slug** (resolved server-side). */
974
+ deleteRelationType(id: string): Promise<void>;
975
+
976
+ // ── Relations (typed edges, FR-1) ──
977
+
978
+ /** Add a directed relation edge between two users. */
979
+ addRelation(options: AddRelationOptions): Promise<Relation>;
980
+ /** Remove a relation edge by its endpoints and relation-type id. */
981
+ removeRelation(fromUserId: string, toUserId: string, relationTypeId: string): Promise<void>;
982
+ /** List relation edges touching a user, optionally filtered by direction. */
983
+ listRelations(options: ListRelationsOptions): Promise<Relation[]>;
984
+
985
+ // ── Profile ──
986
+
987
+ getProfile(userId: string): Promise<Record<string, any>>;
988
+ setProfile(userId: string, data: Record<string, any>): Promise<void>;
989
+
990
+ // ── Auth config ──
991
+
992
+ getAuthConfig(): Promise<AuthConfig>;
993
+ setAuthConfig(config: AuthConfig): Promise<void>;
994
+
995
+ // ── Stewardship (sub-namespace mirroring the runtime bridge) ──
996
+
997
+ readonly stewardship: AuthStewardshipApi;
998
+
999
+ // ── Request-scoped identity + authorization ──
1000
+ //
1001
+ // These operate on the current request's caller context. They're
1002
+ // meaningful inside the runtime; on remote clients they throw (no
1003
+ // per-request context) or fail closed.
1004
+
1005
+ /**
1006
+ * Explicitly bind the caller for the remainder of this request.
1007
+ * Pass a JWT to validate + bind, or `null` / `""` to clear.
1008
+ */
1009
+ setCurrentUser(token: string | null): Promise<void>;
1010
+
1011
+ /**
1012
+ * Snapshot of the currently bound caller. Returns an anonymous caller
1013
+ * (`is_anonymous: true`) when no identity has been bound.
1014
+ */
1015
+ getCurrentUser(): AuthCaller;
1016
+
1017
+ /**
1018
+ * Ask the policy engine whether the bound caller would be allowed to
1019
+ * perform `action` on `resourceId`. Returns a boolean — never throws on
1020
+ * denial. Fails closed when no caller is bound.
1021
+ */
1022
+ can(action: string, resourceId: string, node?: Record<string, unknown> | null): Promise<boolean>;
1023
+
1024
+ /**
1025
+ * Like {@link can}, but returns *why* (FR-7). Fails closed with
1026
+ * `{ allowed: false, reason: 'no bound user' }` when no caller is bound.
1027
+ */
1028
+ explain(action: string, resourceId: string, node?: Record<string, unknown> | null): Promise<PolicyExplain>;
1029
+
1030
+ /**
1031
+ * Batch authorization check (FR-7). Results are returned in the same
1032
+ * order as `checks`. Fails closed (all `{ allowed: false }`) when no
1033
+ * caller is bound.
1034
+ */
1035
+ canMany(checks: CanCheck[]): Promise<{ allowed: boolean }[]>;
450
1036
  }
451
1037
 
452
1038
  // Platform Types
@@ -538,39 +1124,13 @@ export interface Platform {
538
1124
  storage: Storage;
539
1125
  /** Durable multi-step workflows */
540
1126
  workflows: Workflows;
541
- /** Auth service with stewardship, resources, and circle helpers */
542
- auth: {
543
- register(options: { email: string; password: string; profile?: Record<string, any> }): Promise<any>;
544
- login(options: { email: string; password: string }): Promise<any>;
545
- validate(accessToken: string): Promise<any>;
546
- refresh(refreshToken: string): Promise<any>;
547
- logout(sessionId: string): Promise<void>;
548
- getUser(userId: string): Promise<any>;
549
- listUsers(filter?: Record<string, any>): Promise<any>;
550
- updateUser(userId: string, update: Record<string, any>): Promise<any>;
551
- deleteUser(userId: string): Promise<void>;
552
- sendVerification(userId: string): Promise<any>;
553
- verifyEmail(token: string): Promise<void>;
554
- sendPasswordReset(email: string): Promise<any>;
555
- resetPassword(token: string, newPassword: string): Promise<void>;
556
- changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void>;
557
- getFieldConfig(): Promise<any>;
558
- getOAuthUrl(provider: string, options?: { redirectUri?: string }): Promise<any>;
559
- handleOAuthCallback(provider: string, params: { code: string; state: string }): Promise<any>;
560
-
561
- /** Stewardship (guardian/ward delegation) */
562
- stewardship: Stewardship;
563
-
564
- /** List available resources */
565
- listResources(): Promise<Resource[]>;
566
- /** Create a resource definition */
567
- createResource(options: CreateResourceRequest): Promise<Resource>;
568
-
569
- /** Get circle members */
570
- getCircleMembers(circleId: string): Promise<CircleMembership[]>;
571
- /** Get circles a user belongs to */
572
- getUserCircles(userId: string): Promise<Circle[]>;
573
- };
1127
+ /**
1128
+ * Auth service — the full canonical {@link AuthService} surface:
1129
+ * authentication, user management, groups, circles, resources, relation
1130
+ * types, typed relation edges, stewardship, profile/config, and the
1131
+ * request-scoped `can`/`explain`/`canMany` authorization checks.
1132
+ */
1133
+ auth: AuthService;
574
1134
  /** Legacy aliases for compatibility */
575
1135
  env: {
576
1136
  KV: KvStore;
@@ -580,10 +1140,15 @@ export interface Platform {
580
1140
  }
581
1141
 
582
1142
  /**
583
- * Global platform object available in runtime
1143
+ * The ambient `platform` global is declared in `./global.d.ts` (kept
1144
+ * separate so that *type-only* imports of these interfaces — e.g. from
1145
+ * `@maravilla-labs/platform`, which has its own `getPlatform()` global —
1146
+ * do not pull a conflicting ambient `const platform` into scope). Tooling
1147
+ * and app code that want the global reference it explicitly:
1148
+ *
1149
+ * ```ts
1150
+ * /// <reference types="@maravilla-labs/types/global" />
1151
+ * ```
584
1152
  */
585
- declare global {
586
- const platform: Platform;
587
- }
588
1153
 
589
1154
  export { Platform as default };