@company-semantics/contracts 99.3.0 → 101.0.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 +26 -3
- package/src/index.ts +6 -72
- package/src/org/capability-schemas.ts +118 -0
- package/src/org/reporting-write-schemas.ts +71 -0
- package/src/org/schemas.ts +14 -548
- package/src/org/user-org-schemas.ts +75 -0
- package/src/org/workspace-member-schemas.ts +221 -0
- package/src/org/workspace-overview-schemas.ts +92 -0
- package/src/root-exports/README.md +4 -0
- package/src/root-exports/ci-governance.ts +73 -0
- package/src/source-sync/README.md +26 -5
- package/src/source-sync/__tests__/README.md +4 -2
- package/src/source-sync/__tests__/barrel.test.ts +8 -0
- package/src/source-sync/__tests__/scope-projection.test.ts +32 -0
- package/src/source-sync/__tests__/upkeep.test.ts +92 -1
- package/src/source-sync/index.ts +6 -0
- package/src/source-sync/schemas.ts +13 -0
- package/src/source-sync/upkeep.ts +61 -2
- package/src/source-sync/vocabulary.ts +24 -0
- package/src/workflows/__tests__/outcome.test.ts +81 -0
- package/src/workflows/deferred-route.ts +6 -0
- package/src/workflows/outcome-reasons.ts +31 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Member Schemas
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas for the workspace member list and detail, the role catalog, and
|
|
5
|
+
* the member mutation responses (GET /api/workspace/members, DELETE/PATCH
|
|
6
|
+
* /api/workspace/members/:id). Re-exported from `./schemas`, the one import
|
|
7
|
+
* path for every org schema.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { CursorPageSchema } from "../api/primitives";
|
|
11
|
+
import { OrgChartRoleSchema } from "../permissions/orgchart-roles";
|
|
12
|
+
import { AuthorityMutabilitySchema } from "./authority-schemas";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Sub-schemas
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const WorkspaceMemberUnitSummarySchema = z.object({
|
|
19
|
+
unitId: z.string(),
|
|
20
|
+
unitName: z.string(),
|
|
21
|
+
unitPath: z.string(),
|
|
22
|
+
role: z.enum(["owner", "manager", "member"]),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Designation a member holds over a unit they manage (ADR-CTRL-112).
|
|
27
|
+
*
|
|
28
|
+
* Precedence, highest-first: whole_org > admin > unit_owner > delegate > member.
|
|
29
|
+
* The org-wide designations (`whole_org`, `admin`) anchor to the org root
|
|
30
|
+
* unit. This vocabulary is DISPLAY-ONLY — it summarizes management reach for
|
|
31
|
+
* UI surfaces and is NEVER an authorization input (authority resolution uses
|
|
32
|
+
* the orthogonal membership/delegation axes, not this enum).
|
|
33
|
+
*/
|
|
34
|
+
export const OrgUnitDesignationSchema = z.enum([
|
|
35
|
+
"whole_org",
|
|
36
|
+
"admin",
|
|
37
|
+
"unit_owner",
|
|
38
|
+
"delegate",
|
|
39
|
+
"member",
|
|
40
|
+
]);
|
|
41
|
+
export type OrgUnitDesignation = z.infer<typeof OrgUnitDesignationSchema>;
|
|
42
|
+
|
|
43
|
+
export const WorkspaceMemberUnitDesignationEntrySchema = z.object({
|
|
44
|
+
unitId: z.string(),
|
|
45
|
+
unitName: z.string(),
|
|
46
|
+
designation: OrgUnitDesignationSchema,
|
|
47
|
+
/**
|
|
48
|
+
* Whether this designation is REMOVABLE in place (`user_managed` — an explicit
|
|
49
|
+
* overlay grant or `l{N}_unit_owner` membership) or DERIVED from reporting
|
|
50
|
+
* topology (`derived` — the senior person homed in a unit with no manager
|
|
51
|
+
* inside it, ADR-BE-267/294). A `derived` owner can't be revoked: the holder
|
|
52
|
+
* must relinquish it (recorded as a higher-provenance suppression that the
|
|
53
|
+
* structural projection honors) or the upstream chart must change. Display +
|
|
54
|
+
* affordance gating only; never an authorization input.
|
|
55
|
+
*/
|
|
56
|
+
mutability: AuthorityMutabilitySchema,
|
|
57
|
+
});
|
|
58
|
+
export type WorkspaceMemberUnitDesignationEntry = z.infer<
|
|
59
|
+
typeof WorkspaceMemberUnitDesignationEntrySchema
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
const WorkspaceMemberSchema = z.object({
|
|
63
|
+
id: z.string(),
|
|
64
|
+
name: z.string(),
|
|
65
|
+
email: z.string(),
|
|
66
|
+
/**
|
|
67
|
+
* Resolved avatar photo URL for the member, or `null`/absent when none is
|
|
68
|
+
* stored. SOURCE-AGNOSTIC: today the backend resolves this from the Slack
|
|
69
|
+
* profile photo cached on the user record, but the field name deliberately
|
|
70
|
+
* does not encode the origin so future sources (in-app upload, etc.) slot in
|
|
71
|
+
* behind the backend avatar resolver without a contract change. UI MUST fall
|
|
72
|
+
* back to initials when this is absent. Optional because it is an additive
|
|
73
|
+
* display-only enrichment (see ADR-CONTRACTS-055).
|
|
74
|
+
*/
|
|
75
|
+
avatarUrl: z.string().nullable().optional(),
|
|
76
|
+
/** The occupant's Position seat title (org-scoped). Null when the seat is untitled. */
|
|
77
|
+
jobTitle: z.string().nullable(),
|
|
78
|
+
/**
|
|
79
|
+
* The member's Position seat in the org graph — the id every reporting write
|
|
80
|
+
* is keyed on (ADR-CONTRACTS-131). Present here so a user-ADDRESSED surface
|
|
81
|
+
* (the members table, a person picker) can resolve a seat by READ and hand
|
|
82
|
+
* the position straight to `PUT /api/reporting/positions/{positionId}/manager`,
|
|
83
|
+
* instead of a write path minting one with `resolveSeat(create: true)`.
|
|
84
|
+
*
|
|
85
|
+
* Nullable: a member with no materialized seat is a legitimate state, and a
|
|
86
|
+
* read must report that honestly rather than force the server to create one.
|
|
87
|
+
*/
|
|
88
|
+
seatPositionId: z.string().uuid().nullable(),
|
|
89
|
+
/**
|
|
90
|
+
* Display role — the member's highest org-chart standing, or `null` for a
|
|
91
|
+
* plain member with no org-chart authority. Render via `orgChartRoleLabel`.
|
|
92
|
+
*/
|
|
93
|
+
role: OrgChartRoleSchema.nullable(),
|
|
94
|
+
/** Raw RBAC role names (e.g. 'owner', 'admin', 'delegate'). Superset of `role`. */
|
|
95
|
+
roleNames: z.array(z.string()),
|
|
96
|
+
joinedAt: z.string(),
|
|
97
|
+
/** ISO timestamp of last activity; null if never recorded. */
|
|
98
|
+
lastActiveAt: z.string().nullable(),
|
|
99
|
+
/**
|
|
100
|
+
* Home org-unit (`users.primary_unit_id`). Null for org-scoped users
|
|
101
|
+
* (no level-2 home assignment). Used by the unit-detail Members table to
|
|
102
|
+
* roll up "people in this org-unit" across the subtree.
|
|
103
|
+
*/
|
|
104
|
+
primaryUnitId: z.string().uuid().nullable(),
|
|
105
|
+
/** OrgUnit memberships for this member, capped at a small number server-side. */
|
|
106
|
+
unitMemberships: z.array(WorkspaceMemberUnitSummarySchema),
|
|
107
|
+
/**
|
|
108
|
+
* Per-unit org-chart designations the member holds (ADR-CTRL-112). Carries
|
|
109
|
+
* BOTH management-reach entries (whole_org/admin/unit_owner/delegate) and
|
|
110
|
+
* plain contributor entries (`member`); consumers split by designation.
|
|
111
|
+
* Display-only summary; never an authorization input. Empty array when the
|
|
112
|
+
* member holds no per-unit designation.
|
|
113
|
+
*/
|
|
114
|
+
unitDesignations: z.array(WorkspaceMemberUnitDesignationEntrySchema),
|
|
115
|
+
/** True when server truncated `unitMemberships`; detail endpoint returns the full list. */
|
|
116
|
+
unitMembershipsTruncated: z.boolean(),
|
|
117
|
+
/** Invite lifecycle status. `null` for members without an invite record. */
|
|
118
|
+
inviteStatus: z.enum(["active", "pending", "expired"]).nullable(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const MemberRecentActionSchema = z.object({
|
|
122
|
+
id: z.string(),
|
|
123
|
+
timestamp: z.string(),
|
|
124
|
+
action: z.string(),
|
|
125
|
+
summary: z.string(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Which bundle or role contributed a resolved scope (ADR-CTRL-360).
|
|
130
|
+
*
|
|
131
|
+
* `member_default` / `admin` / `account_owner` are the static actor-class
|
|
132
|
+
* bundles AUTH-011C re-homed the deleted role scope lists onto (ADR-BE-184);
|
|
133
|
+
* `role:<name>` is a surviving RBAC role assignment. Open string rather than an
|
|
134
|
+
* enum because `rbac_roles` is data, not a closed vocabulary.
|
|
135
|
+
*
|
|
136
|
+
* A scope carries EVERY band that granted it — composition is a union, not a
|
|
137
|
+
* ladder, so an owner who also holds admin authority legitimately shows both.
|
|
138
|
+
* Display/audit only; `effectiveScopes` remains the authorization surface.
|
|
139
|
+
*/
|
|
140
|
+
const ScopeProvenanceEntrySchema = z.object({
|
|
141
|
+
scope: z.string(),
|
|
142
|
+
bands: z.array(z.string()).min(1),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const WorkspaceMemberDetailSchema = WorkspaceMemberSchema.extend({
|
|
146
|
+
effectiveScopes: z.array(z.string()),
|
|
147
|
+
/**
|
|
148
|
+
* Per-scope provenance for {@link WorkspaceMemberDetailSchema.shape.effectiveScopes}.
|
|
149
|
+
* Optional: absence means the server did not compute it (legacy callsite, or a
|
|
150
|
+
* non-user actor), never that the scopes were granted by nothing.
|
|
151
|
+
*/
|
|
152
|
+
scopeProvenance: z.array(ScopeProvenanceEntrySchema).optional(),
|
|
153
|
+
recentActions: z.array(MemberRecentActionSchema),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Workspace member for the members list. Human users only (no agent actors).
|
|
158
|
+
* Canonical type — inferred from {@link WorkspaceMemberSchema} so the runtime
|
|
159
|
+
* schema is the single source of truth (no parallel hand-written interface).
|
|
160
|
+
*/
|
|
161
|
+
export type WorkspaceMember = z.infer<typeof WorkspaceMemberSchema>;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Full member detail (GET /api/workspace/members/:userId and /api/me self-view).
|
|
165
|
+
* Inferred from {@link WorkspaceMemberDetailSchema}.
|
|
166
|
+
*/
|
|
167
|
+
export type WorkspaceMemberDetail = z.infer<typeof WorkspaceMemberDetailSchema>;
|
|
168
|
+
|
|
169
|
+
export const RoleCatalogEntrySchema = z.object({
|
|
170
|
+
name: z.string(),
|
|
171
|
+
type: z.enum(["system", "custom"]),
|
|
172
|
+
description: z.string(),
|
|
173
|
+
scopes: z.array(z.string()),
|
|
174
|
+
memberCount: z.number().int().nonnegative(),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export const RoleCatalogResponseSchema = z.object({
|
|
178
|
+
roles: z.array(RoleCatalogEntrySchema),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// GET /api/workspace/members
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
export const WorkspaceMembersResponseSchema = CursorPageSchema(
|
|
186
|
+
WorkspaceMemberSchema,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
export type WorkspaceMembersResponse = z.infer<
|
|
190
|
+
typeof WorkspaceMembersResponseSchema
|
|
191
|
+
>;
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// DELETE /api/workspace/members/:id
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
export const RemoveMemberResponseSchema = z.object({
|
|
198
|
+
success: z.boolean(),
|
|
199
|
+
memberId: z.string(),
|
|
200
|
+
memberEmail: z.string(),
|
|
201
|
+
message: z.string(),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
export type RemoveMemberResponse = z.infer<typeof RemoveMemberResponseSchema>;
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// PATCH /api/workspace/members/:id/role
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
export const ChangeMemberRoleResponseSchema = z.object({
|
|
211
|
+
success: z.boolean(),
|
|
212
|
+
memberId: z.string(),
|
|
213
|
+
memberEmail: z.string(),
|
|
214
|
+
previousRole: z.string(),
|
|
215
|
+
newRole: z.string(),
|
|
216
|
+
message: z.string(),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export type ChangeMemberRoleResponse = z.infer<
|
|
220
|
+
typeof ChangeMemberRoleResponseSchema
|
|
221
|
+
>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Overview Schemas
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas for GET /api/workspace (the overview, its marks and owner icon)
|
|
5
|
+
* and GET /api/workspace/access. Re-exported from `./schemas`, the one import
|
|
6
|
+
* path for every org schema.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
const WorkspaceOwnerSchema = z.object({
|
|
11
|
+
id: z.string(),
|
|
12
|
+
name: z.string(),
|
|
13
|
+
email: z.string(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// GET /api/workspace/access
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export const WorkspaceAccessResponseSchema = z.object({
|
|
21
|
+
hasAccess: z.boolean(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type WorkspaceAccessResponse = z.infer<
|
|
25
|
+
typeof WorkspaceAccessResponseSchema
|
|
26
|
+
>;
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// GET /api/workspace
|
|
30
|
+
// PATCH /api/workspace/name (returns WorkspaceOverview)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Icon options for the `owner` org-chart role (ADR-CONT-070). Selected
|
|
35
|
+
* alongside the owner title in the Organization structure editor; `crown` is
|
|
36
|
+
* the default. Presentation only — never an authorization input.
|
|
37
|
+
*/
|
|
38
|
+
export const OrgOwnerIconSchema = z.enum([
|
|
39
|
+
"crown",
|
|
40
|
+
"crown-cross",
|
|
41
|
+
"person-simple-run",
|
|
42
|
+
"person",
|
|
43
|
+
"medal-military",
|
|
44
|
+
"dog",
|
|
45
|
+
"cat",
|
|
46
|
+
"steering-wheel",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* An org's stored marks. See `OrgLogos` for what each slot means and
|
|
51
|
+
* `resolveOrgMark` for the order the square tile prefers them in.
|
|
52
|
+
*/
|
|
53
|
+
export const OrgLogosSchema = z.object({
|
|
54
|
+
appLogoUrl: z.string().nullable(),
|
|
55
|
+
logoUrl: z.string().nullable(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export type OrgLogosShape = z.infer<typeof OrgLogosSchema>;
|
|
59
|
+
|
|
60
|
+
export const WorkspaceOverviewSchema = z.object({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
name: z.string(),
|
|
63
|
+
slug: z.string(),
|
|
64
|
+
type: z.enum(["personal", "shared"]),
|
|
65
|
+
/**
|
|
66
|
+
* The org's two marks. See `OrgLogos` for what each slot means and
|
|
67
|
+
* `resolveOrgMark` for the order the square tile prefers them in.
|
|
68
|
+
*/
|
|
69
|
+
logos: OrgLogosSchema,
|
|
70
|
+
owner: WorkspaceOwnerSchema,
|
|
71
|
+
/**
|
|
72
|
+
* Org-configured display title for the `owner` org-chart role
|
|
73
|
+
* (ADR-CONTRACTS-061). Server-resolved: `orgs.owner_title` when set,
|
|
74
|
+
* otherwise the default "CEO". Always present; pass to
|
|
75
|
+
* `orgChartRoleLabel(role, { ownerTitle })` wherever the owner badge or
|
|
76
|
+
* section heading renders. Presentation only — never an authorization input.
|
|
77
|
+
*/
|
|
78
|
+
ownerTitle: z.string(),
|
|
79
|
+
/**
|
|
80
|
+
* Org-configured icon for the `owner` org-chart role (ADR-CONT-070).
|
|
81
|
+
* Server-resolved: `orgs.owner_icon` when set, otherwise the default
|
|
82
|
+
* "crown". Always present. Presentation only — never an authorization input.
|
|
83
|
+
*/
|
|
84
|
+
ownerIcon: OrgOwnerIconSchema,
|
|
85
|
+
createdAt: z.string(),
|
|
86
|
+
memberCount: z.number(),
|
|
87
|
+
claimable: z.boolean(),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export type OrgOwnerIcon = z.infer<typeof OrgOwnerIconSchema>;
|
|
91
|
+
|
|
92
|
+
export type WorkspaceOverview = z.infer<typeof WorkspaceOverviewSchema>;
|
|
@@ -18,6 +18,7 @@ guard's thresholds; `src/index.ts` re-exports every slice here with
|
|
|
18
18
|
| `org-structure.ts` | sync runs, conflicts, reconciliation, structure findings, transformations, canonical facts, inference, authority |
|
|
19
19
|
| `agent-runtime.ts` | MCP tool discovery, message parts, execution kinds, Ralph, internal admin, rate limits |
|
|
20
20
|
| `platform.ts` | impersonation, usage telemetry, resource keys, errors, the generated OpenAPI surface, security, request tiers |
|
|
21
|
+
| `ci-governance.ts` | guard results and config, SOC 2 compliance, cross-repo CI aggregation, the compatibility manifest |
|
|
21
22
|
|
|
22
23
|
## Invariants
|
|
23
24
|
|
|
@@ -52,10 +53,13 @@ documented in its owning domain (`src/org/`, `src/chat/`, `src/comments/`, …).
|
|
|
52
53
|
- `api`
|
|
53
54
|
- `autotune`
|
|
54
55
|
- `chat`
|
|
56
|
+
- `ci-results`
|
|
55
57
|
- `comments`
|
|
58
|
+
- `compatibility`
|
|
56
59
|
- `errors`
|
|
57
60
|
- `execution`
|
|
58
61
|
- `generated`
|
|
62
|
+
- `guards`
|
|
59
63
|
- `impersonation`
|
|
60
64
|
- `impersonation-events`
|
|
61
65
|
- `internal-admin`
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package root exports — CI governance
|
|
3
|
+
*
|
|
4
|
+
* Guard result and configuration vocabulary, SOC 2 compliance types, cross-repo
|
|
5
|
+
* CI aggregation, and the compatibility manifest.
|
|
6
|
+
*
|
|
7
|
+
* A slice of the `@company-semantics/contracts` root entry, moved here verbatim
|
|
8
|
+
* from `src/index.ts`, which re-exports it with `export *`. Each list is the
|
|
9
|
+
* exact subset of its domain the root exposes. Import from the package root,
|
|
10
|
+
* never from this file.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Guard types (CI guard vocabulary)
|
|
14
|
+
// @see schemas/guard-result.schema.json for JSON Schema
|
|
15
|
+
export type {
|
|
16
|
+
GuardMessage,
|
|
17
|
+
GuardTierResult,
|
|
18
|
+
GuardTier,
|
|
19
|
+
GuardResults,
|
|
20
|
+
CheckResult,
|
|
21
|
+
GuardOutput,
|
|
22
|
+
RoleLimit,
|
|
23
|
+
GuardConfig,
|
|
24
|
+
GuardCheck,
|
|
25
|
+
GuardCheckFactory,
|
|
26
|
+
BoundGuardCheck,
|
|
27
|
+
GuardCheckRegistry,
|
|
28
|
+
StructuralBaselines,
|
|
29
|
+
ScriptsDepsBaseline,
|
|
30
|
+
EvolutionBaselines,
|
|
31
|
+
ContractsFreshnessBaseline,
|
|
32
|
+
MetaBaselines,
|
|
33
|
+
// Aggregate evolution types
|
|
34
|
+
DomainAggregateBaseline,
|
|
35
|
+
AggregateEvolutionConfig,
|
|
36
|
+
// SOC 2 Compliance types
|
|
37
|
+
Soc2ControlArea,
|
|
38
|
+
Soc2ControlStatus,
|
|
39
|
+
Soc2ControlResult,
|
|
40
|
+
Soc2ComplianceOutput,
|
|
41
|
+
// SOC 2 Guard Configuration types
|
|
42
|
+
SecretsDetectionConfig,
|
|
43
|
+
StructuredLoggingConfig,
|
|
44
|
+
AlertsConfigGuardConfig,
|
|
45
|
+
BackupConfigGuardConfig,
|
|
46
|
+
Soc2Baselines,
|
|
47
|
+
} from "../guards/index";
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
GUARD_SCHEMA_VERSION,
|
|
51
|
+
GUARD_TIERS,
|
|
52
|
+
DEFAULT_SKIP_DIRECTORIES,
|
|
53
|
+
DEFAULT_DOMAIN_SECTIONS,
|
|
54
|
+
DEFAULT_INFRA_SECTIONS,
|
|
55
|
+
// SOC 2 Compliance constants
|
|
56
|
+
SOC2_CONTROL_NAMES,
|
|
57
|
+
REQUIRED_SOC2_CONTROLS,
|
|
58
|
+
BLOCKING_SOC2_CONTROLS,
|
|
59
|
+
SOC2_SCHEMA_VERSION,
|
|
60
|
+
} from "../guards/index";
|
|
61
|
+
|
|
62
|
+
// Cross-repo CI aggregation vocabulary
|
|
63
|
+
// @see ADR-CONT-033 for contracts boundary rationale
|
|
64
|
+
// Note: GuardTier is already exported above from './guards/index' (structurally
|
|
65
|
+
// identical); not re-exported here to avoid a duplicate-identifier collision.
|
|
66
|
+
export type { GuardResultPayload, RepoCiResult } from "../ci-results/index";
|
|
67
|
+
|
|
68
|
+
export { assertRepoCiResultConsistent } from "../ci-results/index";
|
|
69
|
+
|
|
70
|
+
// Compatibility manifest (CI guard vocabulary)
|
|
71
|
+
// @see ADR-2025-12-011 for design rationale
|
|
72
|
+
export { COMPATIBILITY } from "../compatibility";
|
|
73
|
+
export type { Compatibility, Deprecation } from "../compatibility";
|
|
@@ -92,9 +92,17 @@ way. This directory holds that shared spelling.
|
|
|
92
92
|
A named person's pre-authorized instruction to keep one scope current, with
|
|
93
93
|
an auto-apply ceiling of `none` or `low` — there is deliberately no higher
|
|
94
94
|
member — a monthly spend cap in integer micro-USD and, when suspended, a
|
|
95
|
-
closed reason
|
|
96
|
-
the grantor
|
|
97
|
-
|
|
95
|
+
closed reason (`SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS`, including
|
|
96
|
+
`grantor_lost_doc_access`: the grantor kept the authority but lost upload and
|
|
97
|
+
edit access on the scope's document). The request that sets one is strict
|
|
98
|
+
and carries no grantor: the grantor is the authenticated caller, and the
|
|
99
|
+
row's grantor label is display-only. The list carries the caller's
|
|
100
|
+
grantability per scope (required); granting uses the same rule, and the same
|
|
101
|
+
blocked-reason vocabulary, as approving a scope.
|
|
102
|
+
- **One blocked-reason vocabulary.** Why the caller may not approve a scope or
|
|
103
|
+
grant its standing order: `needs_approval_authority` (no approval authority —
|
|
104
|
+
an org admin coordinates and never approves) or `needs_doc_access`. Authority
|
|
105
|
+
is checked first, so a caller lacking both reads `needs_approval_authority`.
|
|
98
106
|
- **Drive reach names no credential and no person.** The reach status and the
|
|
99
107
|
confirmed upgrade carry no key material, no delegated identity and no
|
|
100
108
|
address; `hostedDomain` is a Workspace domain, and who changed reach is an
|
|
@@ -273,6 +281,9 @@ in `attention-evidence.ts`.
|
|
|
273
281
|
`pending`, `approved` or `rejected` (no verb writes `rejected` yet).
|
|
274
282
|
- `SOURCE_SYNC_SCOPE_APPROVER_KINDS` / `SourceSyncScopeApproverKind` —
|
|
275
283
|
`org_admin` or `unit_head`.
|
|
284
|
+
- `SOURCE_SYNC_SCOPE_BLOCKED_REASONS` / `SourceSyncScopeBlockedReason` — why
|
|
285
|
+
the caller may not approve a scope or grant its standing order:
|
|
286
|
+
`needs_approval_authority` (checked first) or `needs_doc_access`.
|
|
276
287
|
- `DOCUMENT_TYPES` / `DocumentType` / `KnownDocumentType` — what a source is,
|
|
277
288
|
structurally; `KnownDocumentType` excludes `unknown` (see Document type).
|
|
278
289
|
- `OPERATING_KNOWLEDGE_FAMILIES` / `OperatingKnowledgeFamily` — what kind of
|
|
@@ -369,8 +380,17 @@ in `attention-evidence.ts`.
|
|
|
369
380
|
- `SourceSyncStandingOrderSchema` / `SourceSyncStandingOrder` — one standing
|
|
370
381
|
order: scope, state, ceiling, monthly spend cap, grantor label, when granted
|
|
371
382
|
and, if suspended, why.
|
|
383
|
+
- `SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS` /
|
|
384
|
+
`SourceSyncStandingOrderSuspendedReason` — why an order stopped acting:
|
|
385
|
+
`grantor_lost_authority`, `grantor_lost_doc_access`, `spend_cap_reached` or
|
|
386
|
+
`connection_lost`.
|
|
387
|
+
- `SourceSyncStandingOrderGrantabilitySchema` /
|
|
388
|
+
`SourceSyncStandingOrderGrantability` — whether the caller may grant one
|
|
389
|
+
scope's order; `blockedReason` (from `SOURCE_SYNC_SCOPE_BLOCKED_REASONS`) is
|
|
390
|
+
non-null exactly when it is not grantable. Advisory: the grant re-decides.
|
|
372
391
|
- `SourceSyncStandingOrdersResponseSchema` /
|
|
373
|
-
`SourceSyncStandingOrdersResponse` — the org's standing orders
|
|
392
|
+
`SourceSyncStandingOrdersResponse` — the org's standing orders and the
|
|
393
|
+
caller's required `grantability`.
|
|
374
394
|
- `SourceSyncPutStandingOrderRequestSchema` /
|
|
375
395
|
`SourceSyncPutStandingOrderRequest` — set a scope's order: the ceiling and
|
|
376
396
|
the spend cap, nothing else.
|
|
@@ -394,7 +414,8 @@ in `attention-evidence.ts`.
|
|
|
394
414
|
- `../api/primitives` — the pinned `IsoDateTime`.
|
|
395
415
|
|
|
396
416
|
`rules.ts` imports only `zod`, `../api/primitives` and `./vocabulary`.
|
|
397
|
-
`upkeep.ts`
|
|
417
|
+
`upkeep.ts` imports only `zod`, `../api/primitives` and `./vocabulary`.
|
|
418
|
+
`reach.ts` imports only `zod` and `../api/primitives`.
|
|
398
419
|
`candidate-item.ts` imports only `zod`, `../api/primitives`, `./vocabulary`,
|
|
399
420
|
`./relevance` and `./attention-evidence`.
|
|
400
421
|
`relevance.ts` imports only `zod` and `./enumeration`.
|
|
@@ -37,8 +37,10 @@ Locks the closed Source Sync vocabularies.
|
|
|
37
37
|
- `upkeep.test.ts` — locks staleness findings and standing orders: the six
|
|
38
38
|
finding kinds, the open/resolved/dismissed lifecycle, a required-but-nullable
|
|
39
39
|
dismissal, a ceiling of `none` or `low` and nothing higher, a suspended order
|
|
40
|
-
that says why,
|
|
41
|
-
grantor
|
|
40
|
+
that says why (including `grantor_lost_doc_access`), a strict standing-order
|
|
41
|
+
request that may not name its own grantor, grantability whose blocked reason
|
|
42
|
+
is non-null exactly when a scope is not grantable, and a list that always
|
|
43
|
+
carries the caller's grantability.
|
|
42
44
|
- `reach.test.ts` — locks the Drive-reach wire: reach is user or domain, the
|
|
43
45
|
probe may be unknown, `lastChange: null` parses while an absent key does
|
|
44
46
|
not, an upgrade is exactly `{ confirm: true }`, the upgrade response is the
|
|
@@ -70,6 +70,8 @@ import type {
|
|
|
70
70
|
SourceSyncReachUpgradeRequest,
|
|
71
71
|
SourceSyncReachUpgradeResponse,
|
|
72
72
|
SourceSyncStandingOrder,
|
|
73
|
+
SourceSyncStandingOrderGrantability,
|
|
74
|
+
SourceSyncStandingOrderSuspendedReason,
|
|
73
75
|
SourceSyncStandingOrdersResponse,
|
|
74
76
|
} from "../../index";
|
|
75
77
|
import * as attentionEvidence from "../attention-evidence";
|
|
@@ -162,7 +164,9 @@ const RELEASE_D_RUNTIME_NAMES: ReadonlyArray<
|
|
|
162
164
|
["SourceSyncFindingsResponseSchema", upkeep],
|
|
163
165
|
["SourceSyncDismissFindingRequestSchema", upkeep],
|
|
164
166
|
["SOURCE_SYNC_AUTO_APPLY_CEILINGS", upkeep],
|
|
167
|
+
["SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS", upkeep],
|
|
165
168
|
["SourceSyncStandingOrderSchema", upkeep],
|
|
169
|
+
["SourceSyncStandingOrderGrantabilitySchema", upkeep],
|
|
166
170
|
["SourceSyncStandingOrdersResponseSchema", upkeep],
|
|
167
171
|
["SourceSyncPutStandingOrderRequestSchema", upkeep],
|
|
168
172
|
["SOURCE_SYNC_REACHES", reach],
|
|
@@ -180,7 +184,9 @@ type ReleaseDTypes = [
|
|
|
180
184
|
SourceSyncFindingsResponse,
|
|
181
185
|
SourceSyncDismissFindingRequest,
|
|
182
186
|
SourceSyncAutoApplyCeiling,
|
|
187
|
+
SourceSyncStandingOrderSuspendedReason,
|
|
183
188
|
SourceSyncStandingOrder,
|
|
189
|
+
SourceSyncStandingOrderGrantability,
|
|
184
190
|
SourceSyncStandingOrdersResponse,
|
|
185
191
|
SourceSyncPutStandingOrderRequest,
|
|
186
192
|
SourceSyncReach,
|
|
@@ -197,7 +203,9 @@ const RELEASE_D_TYPE_NAMES = [
|
|
|
197
203
|
"SourceSyncFindingsResponse",
|
|
198
204
|
"SourceSyncDismissFindingRequest",
|
|
199
205
|
"SourceSyncAutoApplyCeiling",
|
|
206
|
+
"SourceSyncStandingOrderSuspendedReason",
|
|
200
207
|
"SourceSyncStandingOrder",
|
|
208
|
+
"SourceSyncStandingOrderGrantability",
|
|
201
209
|
"SourceSyncStandingOrdersResponse",
|
|
202
210
|
"SourceSyncPutStandingOrderRequest",
|
|
203
211
|
"SourceSyncReach",
|
|
@@ -34,6 +34,8 @@ function scope(overrides: Record<string, unknown> = {}) {
|
|
|
34
34
|
vacantHead: false,
|
|
35
35
|
approvedBy: null,
|
|
36
36
|
approvedAt: null,
|
|
37
|
+
approvable: true,
|
|
38
|
+
blockedReason: null,
|
|
37
39
|
...overrides,
|
|
38
40
|
};
|
|
39
41
|
}
|
|
@@ -100,6 +102,36 @@ describe("SourceSyncScopeApprovalProjectionSchema", () => {
|
|
|
100
102
|
});
|
|
101
103
|
});
|
|
102
104
|
|
|
105
|
+
describe("approvable and blockedReason", () => {
|
|
106
|
+
it("accepts a visible scope the caller may not approve, with its reason", () => {
|
|
107
|
+
const parsed = SourceSyncScopeApprovalProjectionSchema.safeParse(
|
|
108
|
+
scope({ approvable: false, blockedReason: "needs_doc_access" }),
|
|
109
|
+
);
|
|
110
|
+
expect(parsed.success).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("a projection may say the caller lacks approval authority", () => {
|
|
114
|
+
expect(
|
|
115
|
+
SourceSyncScopeApprovalProjectionSchema.safeParse(
|
|
116
|
+
scope({ approvable: false, blockedReason: "needs_approval_authority" }),
|
|
117
|
+
).success,
|
|
118
|
+
).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("refuses a reason outside the vocabulary and a missing approvable", () => {
|
|
122
|
+
expect(
|
|
123
|
+
SourceSyncScopeApprovalProjectionSchema.safeParse(
|
|
124
|
+
scope({ approvable: false, blockedReason: "not_admin" }),
|
|
125
|
+
).success,
|
|
126
|
+
).toBe(false);
|
|
127
|
+
const { approvable: _omit, ...withoutApprovable } = scope();
|
|
128
|
+
expect(
|
|
129
|
+
SourceSyncScopeApprovalProjectionSchema.safeParse(withoutApprovable)
|
|
130
|
+
.success,
|
|
131
|
+
).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
103
135
|
describe("scopes is required on every checkpoint response", () => {
|
|
104
136
|
const schemas = [
|
|
105
137
|
["candidate set", SourceSyncCandidateSetResponseSchema],
|
|
@@ -8,14 +8,20 @@
|
|
|
8
8
|
* - A standing order's auto-apply ceiling is none or low; nothing higher parses.
|
|
9
9
|
* - A standing-order request is strict: it may not name a grantor, a user or
|
|
10
10
|
* an actor, because the grantor is the authenticated caller.
|
|
11
|
-
* - A suspended order says why, from a closed set
|
|
11
|
+
* - A suspended order says why, from a closed set that includes the grantor
|
|
12
|
+
* losing access to the scope's document.
|
|
12
13
|
* - Money is an integer micro-USD string.
|
|
14
|
+
* - Grantability names a blocked reason exactly when a scope is not grantable,
|
|
15
|
+
* from the scope blocked-reason vocabulary.
|
|
16
|
+
* - The standing-orders list always carries the caller's grantability.
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
19
|
import { describe, expect, it } from "vitest";
|
|
16
20
|
import {
|
|
17
21
|
SOURCE_SYNC_AUTO_APPLY_CEILINGS,
|
|
18
22
|
SOURCE_SYNC_FINDING_KINDS,
|
|
23
|
+
SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS,
|
|
24
|
+
SourceSyncStandingOrderGrantabilitySchema,
|
|
19
25
|
SourceSyncDismissFindingRequestSchema,
|
|
20
26
|
SourceSyncFindingSchema,
|
|
21
27
|
SourceSyncFindingsResponseSchema,
|
|
@@ -198,9 +204,94 @@ describe("standing orders", () => {
|
|
|
198
204
|
).success,
|
|
199
205
|
).toBe(false);
|
|
200
206
|
}
|
|
207
|
+
expect(
|
|
208
|
+
SourceSyncStandingOrdersResponseSchema.safeParse({
|
|
209
|
+
orders: [order()],
|
|
210
|
+
grantability: [],
|
|
211
|
+
}).success,
|
|
212
|
+
).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("a suspended order may say its grantor lost access to the document", () => {
|
|
216
|
+
expect([...SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS]).toEqual([
|
|
217
|
+
"grantor_lost_authority",
|
|
218
|
+
"grantor_lost_doc_access",
|
|
219
|
+
"spend_cap_reached",
|
|
220
|
+
"connection_lost",
|
|
221
|
+
]);
|
|
222
|
+
expect(
|
|
223
|
+
SourceSyncStandingOrderSchema.safeParse(
|
|
224
|
+
order({
|
|
225
|
+
state: "suspended",
|
|
226
|
+
suspendedReason: "grantor_lost_doc_access",
|
|
227
|
+
}),
|
|
228
|
+
).success,
|
|
229
|
+
).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("grantability names why a scope cannot be granted", () => {
|
|
233
|
+
for (const blockedReason of [
|
|
234
|
+
"needs_approval_authority",
|
|
235
|
+
"needs_doc_access",
|
|
236
|
+
]) {
|
|
237
|
+
expect(
|
|
238
|
+
SourceSyncStandingOrderGrantabilitySchema.safeParse({
|
|
239
|
+
scopeKey: "company",
|
|
240
|
+
grantable: false,
|
|
241
|
+
blockedReason,
|
|
242
|
+
}).success,
|
|
243
|
+
blockedReason,
|
|
244
|
+
).toBe(true);
|
|
245
|
+
}
|
|
246
|
+
expect(
|
|
247
|
+
SourceSyncStandingOrderGrantabilitySchema.safeParse({
|
|
248
|
+
scopeKey: "company",
|
|
249
|
+
grantable: false,
|
|
250
|
+
blockedReason: null,
|
|
251
|
+
}).success,
|
|
252
|
+
).toBe(false);
|
|
253
|
+
expect(
|
|
254
|
+
SourceSyncStandingOrderGrantabilitySchema.safeParse({
|
|
255
|
+
scopeKey: "company",
|
|
256
|
+
grantable: false,
|
|
257
|
+
blockedReason: "felt_like_it",
|
|
258
|
+
}).success,
|
|
259
|
+
).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("a grantable scope carries no blocked reason", () => {
|
|
263
|
+
expect(
|
|
264
|
+
SourceSyncStandingOrderGrantabilitySchema.safeParse({
|
|
265
|
+
scopeKey: "company",
|
|
266
|
+
grantable: true,
|
|
267
|
+
blockedReason: null,
|
|
268
|
+
}).success,
|
|
269
|
+
).toBe(true);
|
|
270
|
+
expect(
|
|
271
|
+
SourceSyncStandingOrderGrantabilitySchema.safeParse({
|
|
272
|
+
scopeKey: "company",
|
|
273
|
+
grantable: true,
|
|
274
|
+
blockedReason: "needs_doc_access",
|
|
275
|
+
}).success,
|
|
276
|
+
).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("the standing-orders list carries the caller's grantability", () => {
|
|
201
280
|
expect(
|
|
202
281
|
SourceSyncStandingOrdersResponseSchema.safeParse({ orders: [order()] })
|
|
203
282
|
.success,
|
|
283
|
+
).toBe(false);
|
|
284
|
+
expect(
|
|
285
|
+
SourceSyncStandingOrdersResponseSchema.safeParse({
|
|
286
|
+
orders: [order()],
|
|
287
|
+
grantability: [
|
|
288
|
+
{
|
|
289
|
+
scopeKey: "company",
|
|
290
|
+
grantable: false,
|
|
291
|
+
blockedReason: "needs_approval_authority",
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
}).success,
|
|
204
295
|
).toBe(true);
|
|
205
296
|
});
|
|
206
297
|
});
|