@company-semantics/contracts 2.1.0 → 2.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1678,6 +1678,57 @@ export interface paths {
1678
1678
  patch?: never;
1679
1679
  trace?: never;
1680
1680
  };
1681
+ "/api/org-units/{unitId}/owners": {
1682
+ parameters: {
1683
+ query?: never;
1684
+ header?: never;
1685
+ path?: never;
1686
+ cookie?: never;
1687
+ };
1688
+ /** List owners of an org unit (local by default; inherited when ?explain=true) */
1689
+ get: operations["listOrgUnitOwners"];
1690
+ put?: never;
1691
+ post?: never;
1692
+ delete?: never;
1693
+ options?: never;
1694
+ head?: never;
1695
+ patch?: never;
1696
+ trace?: never;
1697
+ };
1698
+ "/api/org-units/{unitId}/delegations": {
1699
+ parameters: {
1700
+ query?: never;
1701
+ header?: never;
1702
+ path?: never;
1703
+ cookie?: never;
1704
+ };
1705
+ get?: never;
1706
+ put?: never;
1707
+ /** Grant an explicit delegation to a user on an org unit */
1708
+ post: operations["createOrgUnitDelegation"];
1709
+ delete?: never;
1710
+ options?: never;
1711
+ head?: never;
1712
+ patch?: never;
1713
+ trace?: never;
1714
+ };
1715
+ "/api/org-units/{unitId}/delegations/{id}": {
1716
+ parameters: {
1717
+ query?: never;
1718
+ header?: never;
1719
+ path?: never;
1720
+ cookie?: never;
1721
+ };
1722
+ get?: never;
1723
+ put?: never;
1724
+ post?: never;
1725
+ /** Revoke an existing delegation by id */
1726
+ delete: operations["deleteOrgUnitDelegation"];
1727
+ options?: never;
1728
+ head?: never;
1729
+ patch?: never;
1730
+ trace?: never;
1731
+ };
1681
1732
  "/api/org-units/{unitId}/memberships": {
1682
1733
  parameters: {
1683
1734
  query?: never;
@@ -3469,6 +3520,62 @@ export interface components {
3469
3520
  inheritedFromUnitName: string;
3470
3521
  }[];
3471
3522
  };
3523
+ OrgUnitOwnersResponse: {
3524
+ owners: {
3525
+ /** Format: uuid */
3526
+ userId: string;
3527
+ fullName: string;
3528
+ jobTitle: string | null;
3529
+ slackAvatarUrl: string | null;
3530
+ /** @enum {string} */
3531
+ authoritySource: "structural" | "delegated" | "manual_membership" | "org_admin";
3532
+ /** @enum {string} */
3533
+ authorityOrigin: "topology" | "delegation" | "administrative_override";
3534
+ isLocal: boolean;
3535
+ /** Format: uuid */
3536
+ derivedFromUnitId: string;
3537
+ derivedFromUnitName?: string;
3538
+ derivedFromUserId?: string | null;
3539
+ derivationMetadata?: {
3540
+ [key: string]: unknown;
3541
+ };
3542
+ }[];
3543
+ inherited?: {
3544
+ /** Format: uuid */
3545
+ userId: string;
3546
+ fullName: string;
3547
+ jobTitle: string | null;
3548
+ slackAvatarUrl: string | null;
3549
+ /** @enum {string} */
3550
+ authoritySource: "structural" | "delegated" | "manual_membership" | "org_admin";
3551
+ /** @enum {string} */
3552
+ authorityOrigin: "topology" | "delegation" | "administrative_override";
3553
+ isLocal: boolean;
3554
+ /** Format: uuid */
3555
+ derivedFromUnitId: string;
3556
+ derivedFromUnitName?: string;
3557
+ derivedFromUserId?: string | null;
3558
+ derivationMetadata?: {
3559
+ [key: string]: unknown;
3560
+ };
3561
+ }[];
3562
+ };
3563
+ Delegation: {
3564
+ /** Format: uuid */
3565
+ id: string;
3566
+ /** Format: uuid */
3567
+ orgId: string;
3568
+ /** Format: uuid */
3569
+ unitId: string;
3570
+ /** Format: uuid */
3571
+ userId: string;
3572
+ grantedBy: string | null;
3573
+ grantedAt: string;
3574
+ revokedAt: string | null;
3575
+ /** @enum {string} */
3576
+ status: "active" | "revoked";
3577
+ note: string | null;
3578
+ };
3472
3579
  OrgUnitMembershipListResponse: {
3473
3580
  /** Format: uuid */
3474
3581
  unitId: string;
@@ -6377,6 +6484,79 @@ export interface operations {
6377
6484
  };
6378
6485
  };
6379
6486
  };
6487
+ listOrgUnitOwners: {
6488
+ parameters: {
6489
+ query?: never;
6490
+ header?: never;
6491
+ path: {
6492
+ unitId: string;
6493
+ };
6494
+ cookie?: never;
6495
+ };
6496
+ requestBody?: never;
6497
+ responses: {
6498
+ /** @description Owners hydrated with profile fields. Includes `inherited` when ?explain=true. */
6499
+ 200: {
6500
+ headers: {
6501
+ [name: string]: unknown;
6502
+ };
6503
+ content: {
6504
+ "application/json": components["schemas"]["OrgUnitOwnersResponse"];
6505
+ };
6506
+ };
6507
+ };
6508
+ };
6509
+ createOrgUnitDelegation: {
6510
+ parameters: {
6511
+ query?: never;
6512
+ header?: never;
6513
+ path: {
6514
+ unitId: string;
6515
+ };
6516
+ cookie?: never;
6517
+ };
6518
+ requestBody: {
6519
+ content: {
6520
+ "application/json": {
6521
+ /** Format: uuid */
6522
+ userId: string;
6523
+ note?: string;
6524
+ };
6525
+ };
6526
+ };
6527
+ responses: {
6528
+ /** @description Created delegation row */
6529
+ 201: {
6530
+ headers: {
6531
+ [name: string]: unknown;
6532
+ };
6533
+ content: {
6534
+ "application/json": components["schemas"]["Delegation"];
6535
+ };
6536
+ };
6537
+ };
6538
+ };
6539
+ deleteOrgUnitDelegation: {
6540
+ parameters: {
6541
+ query?: never;
6542
+ header?: never;
6543
+ path: {
6544
+ unitId: string;
6545
+ id: string;
6546
+ };
6547
+ cookie?: never;
6548
+ };
6549
+ requestBody?: never;
6550
+ responses: {
6551
+ /** @description Delegation revoked */
6552
+ 204: {
6553
+ headers: {
6554
+ [name: string]: unknown;
6555
+ };
6556
+ content?: never;
6557
+ };
6558
+ };
6559
+ };
6380
6560
  listOrgUnitMemberships: {
6381
6561
  parameters: {
6382
6562
  query?: never;
package/src/index.ts CHANGED
@@ -347,12 +347,16 @@ export { ROLE_DISPLAY_MAP, VIEW_SCOPE_MAP, getViewScope, TRANSFER_RESPONSIBILITI
347
347
  // View authorization types (Phase 5 - ADR-APP-013)
348
348
  export type { AuthorizableView } from './org/index'
349
349
 
350
- // Authority & Delegation vocabulary (PRD-00622)
350
+ // Authority & Delegation vocabulary
351
351
  export {
352
352
  AuthoritySourceSchema,
353
353
  AuthorityOriginSchema,
354
354
  AuthorityConfidenceSchema,
355
- OwnerRecordSchema,
355
+ AuthorityMechanismSchema,
356
+ AuthorityLocalitySchema,
357
+ AuthorityMutabilitySchema,
358
+ OwnerAuthoritySchema,
359
+ OrgUnitOwnerSchema,
356
360
  OrgUnitOwnersResponseSchema,
357
361
  DelegationSchema,
358
362
  CreateDelegationRequestSchema,
@@ -362,7 +366,11 @@ export type {
362
366
  AuthoritySource,
363
367
  AuthorityOrigin,
364
368
  AuthorityConfidence,
365
- OwnerRecord,
369
+ AuthorityMechanism,
370
+ AuthorityLocality,
371
+ AuthorityMutability,
372
+ OwnerAuthority,
373
+ OrgUnitOwner,
366
374
  OrgUnitOwnersResponse,
367
375
  Delegation,
368
376
  CreateDelegationRequest,
package/src/org/index.ts CHANGED
@@ -270,12 +270,16 @@ export type {
270
270
  UpdateOrgUnitResponse,
271
271
  } from './schemas';
272
272
 
273
- // Authority & Delegation vocabulary (PRD-00622)
273
+ // Authority & Delegation vocabulary
274
274
  export {
275
275
  AuthoritySourceSchema,
276
276
  AuthorityOriginSchema,
277
277
  AuthorityConfidenceSchema,
278
- OwnerRecordSchema,
278
+ AuthorityMechanismSchema,
279
+ AuthorityLocalitySchema,
280
+ AuthorityMutabilitySchema,
281
+ OwnerAuthoritySchema,
282
+ OrgUnitOwnerSchema,
279
283
  OrgUnitOwnersResponseSchema,
280
284
  DelegationSchema,
281
285
  CreateDelegationRequestSchema,
@@ -285,7 +289,11 @@ export type {
285
289
  AuthoritySource,
286
290
  AuthorityOrigin,
287
291
  AuthorityConfidence,
288
- OwnerRecord,
292
+ AuthorityMechanism,
293
+ AuthorityLocality,
294
+ AuthorityMutability,
295
+ OwnerAuthority,
296
+ OrgUnitOwner,
289
297
  OrgUnitOwnersResponse,
290
298
  Delegation,
291
299
  CreateDelegationRequest,
@@ -759,19 +759,25 @@ export const OrgUnitMembershipSchema = z.object({
759
759
  });
760
760
 
761
761
  // ---------------------------------------------------------------------------
762
- // Authority & Delegation vocabulary (PRD-00622)
762
+ // Authority & Delegation vocabulary
763
763
  //
764
- // Models effective owner attribution across the org topology: structural
765
- // membership (l1..l5 unit owner roles), delegated authority, manual
766
- // membership, and administrative override (org_admin). The `authoritySource`
767
- // + `authorityOrigin` pair lets UI explain *why* a user appears as an
768
- // owner without re-running the resolver.
764
+ // The API expresses authority as orthogonal axes (mechanism × locality ×
765
+ // mutability) instead of a single overloaded `source` enum, so future
766
+ // combinations like inherited-delegated or hris-delegated compose by
767
+ // construction rather than forcing each consumer to add a new switch case.
768
+ // The engine's internal AuthoritySource enum stays narrower than the wire
769
+ // shape — it's debug/audit vocabulary, not API vocabulary.
769
770
  // ---------------------------------------------------------------------------
770
771
 
772
+ /**
773
+ * Engine-internal authority source. Used by the projection storage layer for
774
+ * debugging and drift records; not part of the public API response shape.
775
+ * `manual_membership` was removed when the legacy `l{N}_unit_owner` track was
776
+ * canonicalized onto delegations.
777
+ */
771
778
  export const AuthoritySourceSchema = z.enum([
772
779
  'structural',
773
780
  'delegated',
774
- 'manual_membership',
775
781
  'org_admin',
776
782
  ]);
777
783
  export type AuthoritySource = z.infer<typeof AuthoritySourceSchema>;
@@ -786,24 +792,79 @@ export type AuthorityOrigin = z.infer<typeof AuthorityOriginSchema>;
786
792
  export const AuthorityConfidenceSchema = z.enum(['authoritative', 'inferred']);
787
793
  export type AuthorityConfidence = z.infer<typeof AuthorityConfidenceSchema>;
788
794
 
789
- export const OwnerRecordSchema = z.object({
790
- userId: z.string().uuid(),
791
- fullName: z.string().min(1),
792
- jobTitle: z.string().nullable(),
793
- slackAvatarUrl: z.string().nullable(),
794
- authoritySource: AuthoritySourceSchema,
795
- authorityOrigin: AuthorityOriginSchema,
796
- isLocal: z.boolean(),
795
+ /** How the authority arose. */
796
+ export const AuthorityMechanismSchema = z.enum([
797
+ 'structural',
798
+ 'delegated',
799
+ 'rbac',
800
+ ]);
801
+ export type AuthorityMechanism = z.infer<typeof AuthorityMechanismSchema>;
802
+
803
+ /** Whether the grant lives on the target unit or is inherited from an ancestor. */
804
+ export const AuthorityLocalitySchema = z.enum(['local', 'inherited']);
805
+ export type AuthorityLocality = z.infer<typeof AuthorityLocalitySchema>;
806
+
807
+ /**
808
+ * Whether the user can change this authority from this surface.
809
+ * - `derived`: change the upstream source instead (the org chart, RBAC roles).
810
+ * - `user_managed`: removable from this UI.
811
+ */
812
+ export const AuthorityMutabilitySchema = z.enum(['derived', 'user_managed']);
813
+ export type AuthorityMutability = z.infer<typeof AuthorityMutabilitySchema>;
814
+
815
+ export const OwnerAuthoritySchema = z.object({
816
+ mechanism: AuthorityMechanismSchema,
817
+ locality: AuthorityLocalitySchema,
818
+ mutability: AuthorityMutabilitySchema,
797
819
  derivedFromUnitId: z.string().uuid(),
798
820
  derivedFromUnitName: z.string().min(1).optional(),
799
821
  derivedFromUserId: z.string().uuid().nullable().optional(),
822
+ /**
823
+ * Human-readable reason this authority applies, built at serialize time from
824
+ * the strategy's derivation metadata. Foundation for trust UI, debugging,
825
+ * AI explainability, governance reports, and audits.
826
+ */
827
+ explanation: z.string(),
828
+ /**
829
+ * Strategy-specific structured metadata, preserved verbatim for tooling that
830
+ * needs more than the explanation string (drift detection, audits).
831
+ */
800
832
  derivationMetadata: z.record(z.string(), z.unknown()).optional(),
833
+ /**
834
+ * `org_unit_delegations.id` when `mechanism === 'delegated'`. Surfaced
835
+ * separately so the UI doesn't need to inspect derivationMetadata to render
836
+ * the revoke action.
837
+ */
838
+ delegationId: z.string().uuid().optional(),
839
+ });
840
+ export type OwnerAuthority = z.infer<typeof OwnerAuthoritySchema>;
841
+
842
+ export const OrgUnitOwnerSchema = z.object({
843
+ userId: z.string().uuid(),
844
+ fullName: z.string().min(1),
845
+ jobTitle: z.string().nullable(),
846
+ slackAvatarUrl: z.string().nullable(),
847
+ /** All authority grants this user holds for the unit. At least one entry. */
848
+ authorities: z.array(OwnerAuthoritySchema).min(1),
801
849
  });
802
- export type OwnerRecord = z.infer<typeof OwnerRecordSchema>;
850
+ export type OrgUnitOwner = z.infer<typeof OrgUnitOwnerSchema>;
851
+
852
+ /**
853
+ * Contract-level invariant: each owner list contains each userId at most once.
854
+ * Duplicate userIds are a serializer bug, not a presentation concern, so we
855
+ * fail Zod parse at the API boundary on both server and client.
856
+ */
857
+ const uniqueByUserId = (arr: ReadonlyArray<{ userId: string }>): boolean =>
858
+ new Set(arr.map((o) => o.userId)).size === arr.length;
803
859
 
804
860
  export const OrgUnitOwnersResponseSchema = z.object({
805
- owners: z.array(OwnerRecordSchema),
806
- inherited: z.array(OwnerRecordSchema).optional(),
861
+ owners: z
862
+ .array(OrgUnitOwnerSchema)
863
+ .refine(uniqueByUserId, { message: 'duplicate userId in owners' }),
864
+ inherited: z
865
+ .array(OrgUnitOwnerSchema)
866
+ .refine(uniqueByUserId, { message: 'duplicate userId in inherited' })
867
+ .optional(),
807
868
  });
808
869
  export type OrgUnitOwnersResponse = z.infer<typeof OrgUnitOwnersResponseSchema>;
809
870