@company-semantics/contracts 62.0.0 → 62.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 +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +6 -0
- package/src/org/README.md +2 -0
- package/src/org/__tests__/position-reporting.test.ts +15 -0
- package/src/org/index.ts +2 -0
- package/src/org/position-reporting.ts +24 -0
- package/src/org/schemas.ts +38 -26
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = '8e4ad15ffd00' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = '8e4ad15ffd00b9b83f7adb26bd7bfdff0c03994756b3022b9dbc27f3ba9f7852' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -5646,8 +5646,11 @@ export interface components {
|
|
|
5646
5646
|
updatedAt: string;
|
|
5647
5647
|
depth: number;
|
|
5648
5648
|
hasChildren: boolean;
|
|
5649
|
+
/** @description Accounts with a home assignment, rolled up over this unit's subtree. Legitimately 0 for a fully staffed department in an HRIS-imported org, where almost nobody has signed in — use placedPeopleCount for headcount. */
|
|
5649
5650
|
memberCount: number;
|
|
5651
|
+
/** @description Active open roles (`open` + `hiring`) rolled up over this unit's subtree. Vacant seats only — an HRIS-imported seat is `lifecycle_owner='import:hris'` and is counted by neither this nor memberCount. */
|
|
5650
5652
|
openRoleCount: number;
|
|
5653
|
+
/** @description Distinct people holding a live canonical seat in this unit's subtree, whether or not they have an account. Deliberately does NOT filter positions.status: an occupied `closed` seat still draws a card on the org chart, so a filter here would disagree with it. */
|
|
5651
5654
|
placedPeopleCount: number;
|
|
5652
5655
|
missingAtNextLevel: {
|
|
5653
5656
|
count: number;
|
|
@@ -5795,8 +5798,11 @@ export interface components {
|
|
|
5795
5798
|
updatedAt: string;
|
|
5796
5799
|
depth: number;
|
|
5797
5800
|
hasChildren: boolean;
|
|
5801
|
+
/** @description Accounts with a home assignment, rolled up over this unit's subtree. Legitimately 0 for a fully staffed department in an HRIS-imported org, where almost nobody has signed in — use placedPeopleCount for headcount. */
|
|
5798
5802
|
memberCount: number;
|
|
5803
|
+
/** @description Active open roles (`open` + `hiring`) rolled up over this unit's subtree. Vacant seats only — an HRIS-imported seat is `lifecycle_owner='import:hris'` and is counted by neither this nor memberCount. */
|
|
5799
5804
|
openRoleCount: number;
|
|
5805
|
+
/** @description Distinct people holding a live canonical seat in this unit's subtree, whether or not they have an account. Deliberately does NOT filter positions.status: an occupied `closed` seat still draws a card on the org chart, so a filter here would disagree with it. */
|
|
5800
5806
|
placedPeopleCount: number;
|
|
5801
5807
|
missingAtNextLevel: {
|
|
5802
5808
|
count: number;
|
package/src/org/README.md
CHANGED
|
@@ -335,6 +335,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
335
335
|
- `RemoveMemberRequest` _(type)_ — Request payload for removing a member from the workspace.
|
|
336
336
|
- `RemoveMemberResponse` _(type)_
|
|
337
337
|
- `RemoveMemberResponseSchema`
|
|
338
|
+
- `ReportingErrorCode` _(type)_
|
|
339
|
+
- `ReportingErrorCodeSchema` — Structured codes for reporting-line mutation rejections, surfaced as `ErrorResponse.meta.reportingCode` by…
|
|
338
340
|
- `ResolveOrgDivergenceResult` _(type)_
|
|
339
341
|
- `ResolveOrgDivergenceResultSchema`
|
|
340
342
|
- `RoleCatalogEntry` _(type)_ — Entry in the RBAC roles catalog (GET /api/rbac/roles).
|
|
@@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
PositionReportingSchema,
|
|
4
4
|
PositionReportingRelationshipTypeSchema,
|
|
5
|
+
ReportingErrorCodeSchema,
|
|
5
6
|
} from "../position-reporting.js";
|
|
6
7
|
|
|
7
8
|
const validEdge = {
|
|
@@ -86,3 +87,17 @@ describe("PositionReportingRelationshipTypeSchema", () => {
|
|
|
86
87
|
).toBe(false);
|
|
87
88
|
});
|
|
88
89
|
});
|
|
90
|
+
|
|
91
|
+
describe("Enum exhaustiveness", () => {
|
|
92
|
+
it("ReportingErrorCodeSchema enumerates all 3 known codes", () => {
|
|
93
|
+
const codes = [
|
|
94
|
+
"REPORTING_CYCLE_BLOCKED",
|
|
95
|
+
"ENDPOINT_NOT_ON_CHART",
|
|
96
|
+
"SELF_REPORT_BLOCKED",
|
|
97
|
+
];
|
|
98
|
+
for (const code of codes) {
|
|
99
|
+
expect(() => ReportingErrorCodeSchema.parse(code)).not.toThrow();
|
|
100
|
+
}
|
|
101
|
+
expect(() => ReportingErrorCodeSchema.parse("OTHER")).toThrow();
|
|
102
|
+
});
|
|
103
|
+
});
|
package/src/org/index.ts
CHANGED
|
@@ -115,10 +115,12 @@ export type { PositionStatus, Position } from "./positions";
|
|
|
115
115
|
export {
|
|
116
116
|
PositionReportingRelationshipTypeSchema,
|
|
117
117
|
PositionReportingSchema,
|
|
118
|
+
ReportingErrorCodeSchema,
|
|
118
119
|
} from "./position-reporting";
|
|
119
120
|
export type {
|
|
120
121
|
PositionReportingRelationshipType,
|
|
121
122
|
PositionReporting,
|
|
123
|
+
ReportingErrorCode,
|
|
122
124
|
} from "./position-reporting";
|
|
123
125
|
|
|
124
126
|
// Source authority: per-field ownership — who may WRITE a given entity field.
|
|
@@ -61,3 +61,27 @@ export const PositionReportingSchema = z.object({
|
|
|
61
61
|
provenance: FactProvenanceSchema.optional(),
|
|
62
62
|
});
|
|
63
63
|
export type PositionReporting = z.infer<typeof PositionReportingSchema>;
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// ReportingErrorCode — structured rejection codes for reporting-line mutations
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Structured codes for reporting-line mutation rejections, surfaced as
|
|
71
|
+
* `ErrorResponse.meta.reportingCode` by `PUT /api/reporting/positions/{id}/manager`
|
|
72
|
+
* and its preview twin. The top-level `error` field only carries the error
|
|
73
|
+
* class (`CONFLICT`, `VALIDATION_ERROR`), so this code is the one channel that
|
|
74
|
+
* lets a client tell a cycle 409 from a not-on-chart 409 and key per-code copy
|
|
75
|
+
* off it (the `OrgUnitErrorCode` lesson). The closed set ships all known codes
|
|
76
|
+
* at once: adding a member later is a breaking change for consumers holding an
|
|
77
|
+
* exhaustive `Record<ReportingErrorCode, …>`.
|
|
78
|
+
*/
|
|
79
|
+
export const ReportingErrorCodeSchema = z.enum([
|
|
80
|
+
// 409 — the edge would close a reporting cycle on the position graph.
|
|
81
|
+
"REPORTING_CYCLE_BLOCKED",
|
|
82
|
+
// 409 — an endpoint of the edge has no seat on the org chart yet.
|
|
83
|
+
"ENDPOINT_NOT_ON_CHART",
|
|
84
|
+
// 400 — a seat (or member) cannot report to itself.
|
|
85
|
+
"SELF_REPORT_BLOCKED",
|
|
86
|
+
]);
|
|
87
|
+
export type ReportingErrorCode = z.infer<typeof ReportingErrorCodeSchema>;
|
package/src/org/schemas.ts
CHANGED
|
@@ -1582,36 +1582,48 @@ export const OrgUnitTreeNodeSchema = OrgUnitSchema.extend({
|
|
|
1582
1582
|
depth: z.number().int().min(1).max(5),
|
|
1583
1583
|
hasChildren: z.boolean(),
|
|
1584
1584
|
/**
|
|
1585
|
-
*
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1585
|
+
* WHAT it counts is the `.describe()` below — the single field-level
|
|
1586
|
+
* description, which reaches `openapi/backend.yaml` and the generated API
|
|
1587
|
+
* types. This comment carries only WHY, so the two cannot drift into
|
|
1588
|
+
* disagreeing.
|
|
1589
1589
|
*
|
|
1590
|
-
*
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1590
|
+
* Why accounts: it is the right population for anything that needs an account
|
|
1591
|
+
* to act on — the share dialog's fanout, the unit-scoped members table. It is
|
|
1592
|
+
* backed by `home_assignments`, whose `user_id` is NOT NULL, so an accountless
|
|
1593
|
+
* person structurally cannot appear in it. Deliberately NOT redefined and NOT
|
|
1594
|
+
* deprecated; {@link placedPeopleCount} answers the headcount question, and the
|
|
1595
|
+
* two legitimately disagree (ADR-CONTRACTS-150, ADR-BE-657).
|
|
1595
1596
|
*/
|
|
1596
|
-
memberCount: z
|
|
1597
|
+
memberCount: z
|
|
1598
|
+
.number()
|
|
1599
|
+
.int()
|
|
1600
|
+
.min(0)
|
|
1601
|
+
.describe(
|
|
1602
|
+
"Accounts with a home assignment, rolled up over this unit's subtree. Legitimately 0 for a fully staffed department in an HRIS-imported org, where almost nobody has signed in — use placedPeopleCount for headcount.",
|
|
1603
|
+
),
|
|
1604
|
+
/** Drives the "(N + M open)" count split in the people surfaces. */
|
|
1605
|
+
openRoleCount: z
|
|
1606
|
+
.number()
|
|
1607
|
+
.int()
|
|
1608
|
+
.min(0)
|
|
1609
|
+
.describe(
|
|
1610
|
+
"Active open roles (`open` + `hiring`) rolled up over this unit's subtree. Vacant seats only — an HRIS-imported seat is `lifecycle_owner='import:hris'` and is counted by neither this nor memberCount.",
|
|
1611
|
+
),
|
|
1597
1612
|
/**
|
|
1598
|
-
*
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1613
|
+
* Predicate: `placement_role='canonical'`, `status <> 'ended'`, keyed by
|
|
1614
|
+
* `positions.unit_id`. Placement and home are independent graphs
|
|
1615
|
+
* (ADR-CTRL-159), so a person seated here may be homed elsewhere and vice
|
|
1616
|
+
* versa. The `positions.status` non-filter stated in the `.describe()` below
|
|
1617
|
+
* is deliberate and load-bearing — the backend pins it with a test whose name
|
|
1618
|
+
* says so (ADR-BE-657).
|
|
1601
1619
|
*/
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
* Placement and home are independent graphs (ADR-CTRL-159): a person seated
|
|
1610
|
-
* here may be homed elsewhere, and vice versa. Deliberately does NOT filter
|
|
1611
|
-
* `positions.status` — an occupied `closed` seat still draws a card on the
|
|
1612
|
-
* org chart, and a filter here would disagree with it (ADR-BE-657).
|
|
1613
|
-
*/
|
|
1614
|
-
placedPeopleCount: z.number().int().min(0),
|
|
1620
|
+
placedPeopleCount: z
|
|
1621
|
+
.number()
|
|
1622
|
+
.int()
|
|
1623
|
+
.min(0)
|
|
1624
|
+
.describe(
|
|
1625
|
+
"Distinct people holding a live canonical seat in this unit's subtree, whether or not they have an account. Deliberately does NOT filter positions.status: an occupied `closed` seat still draws a card on the org chart, so a filter here would disagree with it.",
|
|
1626
|
+
),
|
|
1615
1627
|
missingAtNextLevel: MissingAtNextLevelSchema.nullable(),
|
|
1616
1628
|
});
|
|
1617
1629
|
|