@company-semantics/contracts 100.0.0 → 101.1.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 +21 -7
- 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/schedules/README.md +9 -0
- package/src/schedules/__tests__/api.test.ts +51 -0
- package/src/schedules/__tests__/schedules-exports.test.ts +3 -0
- package/src/schedules/api.ts +73 -0
- package/src/schedules/index.ts +7 -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 +8 -0
- package/src/source-sync/__tests__/upkeep.test.ts +92 -1
- package/src/source-sync/index.ts +4 -0
- package/src/source-sync/schemas.ts +7 -4
- package/src/source-sync/upkeep.ts +61 -2
- package/src/source-sync/vocabulary.ts +19 -5
- package/src/workflows/__tests__/outcome.test.ts +81 -0
- package/src/workflows/deferred-route.ts +4 -0
- package/src/workflows/outcome-reasons.ts +26 -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";
|
package/src/schedules/README.md
CHANGED
|
@@ -23,6 +23,8 @@ ledger and reconciles) can never disagree about a slot.
|
|
|
23
23
|
`contentHash` is computed over.
|
|
24
24
|
- Response schemas only; request bodies live in the backend beside their routes
|
|
25
25
|
(ScheduleTickRequest is a type, not a schema).
|
|
26
|
+
- A re-prove grants exactly one extra attempt for a named execution; the route
|
|
27
|
+
only grants, the backend clock dispatches.
|
|
26
28
|
- Pure: no clock reads, no I/O, only `zod`.
|
|
27
29
|
|
|
28
30
|
## Dependencies
|
|
@@ -64,6 +66,9 @@ ledger and reconciles) can never disagree about a slot.
|
|
|
64
66
|
- `SCHEDULE_MODES` — off: GitHub cron only, clock ignores it. shadow: clock plans, never dispatches. live: clock dispatches…
|
|
65
67
|
- `SCHEDULE_OUTPUT_VERIFIERS` — Closed catalog. v0 has one verifier.
|
|
66
68
|
- `SCHEDULE_REGISTRY_SCHEMA_VERSION`
|
|
69
|
+
- `SCHEDULE_REPROVE_PATH` — The operator re-prove route (control ADR slug operator-schedule-reprove).
|
|
70
|
+
- `SCHEDULE_REPROVE_REASON_MAX_LENGTH` — Upper bound of a re-prove reason (operator prose, kept for audit only).
|
|
71
|
+
- `SCHEDULE_REPROVE_REFUSALS` — Why a re-prove was refused.
|
|
67
72
|
- `SCHEDULE_RUN_CONCLUSIONS`
|
|
68
73
|
- `SCHEDULE_RUN_NAME` — The exact `run-name:` a registered workflow declares, so displaytitle = executionid#attempt.
|
|
69
74
|
- `SCHEDULE_SEVERITIES`
|
|
@@ -91,6 +96,10 @@ ledger and reconciles) can never disagree about a slot.
|
|
|
91
96
|
- `ScheduleOutputVerifier` _(type)_
|
|
92
97
|
- `ScheduleRegistrySnapshot` _(type)_
|
|
93
98
|
- `ScheduleRegistrySnapshotSchema`
|
|
99
|
+
- `ScheduleReproveRefusal` _(type)_
|
|
100
|
+
- `ScheduleReproveRequest` _(type)_ — Re-prove request body, as a TYPE only: the backend binds its zod schema to it with `satisfies…
|
|
101
|
+
- `ScheduleReproveResponse` _(type)_
|
|
102
|
+
- `ScheduleReproveResponseSchema` — `granted`: the clock will dispatch `<executionId>#<grantedAttempt>` on its next pass.
|
|
94
103
|
- `ScheduleRunConclusion` _(type)_
|
|
95
104
|
- `ScheduleSeverity` _(type)_
|
|
96
105
|
- `ScheduleTickRequest` _(type)_ — Tick request body, as a TYPE only: request schemas stay in the backend beside the route (contracts…
|
|
@@ -5,9 +5,12 @@ import {
|
|
|
5
5
|
SCHEDULE_METRIC_TICK,
|
|
6
6
|
SCHEDULE_METRIC_TICK_SOURCE_DIMENSION,
|
|
7
7
|
SCHEDULE_METRIC_ENVIRONMENT_DIMENSION,
|
|
8
|
+
SCHEDULE_REPROVE_PATH,
|
|
9
|
+
SCHEDULE_REPROVE_REFUSALS,
|
|
8
10
|
SCHEDULE_TICK_PATH,
|
|
9
11
|
ScheduleClaimResponseSchema,
|
|
10
12
|
ScheduleFinishResponseSchema,
|
|
13
|
+
ScheduleReproveResponseSchema,
|
|
11
14
|
ScheduleTickResponseSchema,
|
|
12
15
|
} from "../api";
|
|
13
16
|
|
|
@@ -69,4 +72,52 @@ describe("schedules api responses", () => {
|
|
|
69
72
|
"environment",
|
|
70
73
|
]);
|
|
71
74
|
});
|
|
75
|
+
|
|
76
|
+
it("grants a re-prove for a named attempt", () => {
|
|
77
|
+
expect(SCHEDULE_REPROVE_PATH).toBe("/api/schedules/executions/reprove");
|
|
78
|
+
expect(
|
|
79
|
+
ScheduleReproveResponseSchema.safeParse({
|
|
80
|
+
outcome: "granted",
|
|
81
|
+
executionId: "control-pin-audit/2026-10-12T15:00:00Z",
|
|
82
|
+
grantedAttempt: 4,
|
|
83
|
+
incidentKey:
|
|
84
|
+
"schedule:execution:control-pin-audit/2026-10-12T15:00:00Z",
|
|
85
|
+
incidentNoted: true,
|
|
86
|
+
}).success,
|
|
87
|
+
).toBe(true);
|
|
88
|
+
expect(
|
|
89
|
+
ScheduleReproveResponseSchema.safeParse({
|
|
90
|
+
outcome: "granted",
|
|
91
|
+
executionId: "control-pin-audit/2026-10-12T15:00:00Z",
|
|
92
|
+
grantedAttempt: 1,
|
|
93
|
+
incidentKey: null,
|
|
94
|
+
incidentNoted: false,
|
|
95
|
+
}).success,
|
|
96
|
+
).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("refuses a re-prove with a closed reason", () => {
|
|
100
|
+
expect(SCHEDULE_REPROVE_REFUSALS).toEqual([
|
|
101
|
+
"unknown_execution",
|
|
102
|
+
"schedule_not_live",
|
|
103
|
+
"not_exhausted",
|
|
104
|
+
"not_reprovable",
|
|
105
|
+
"schedule_busy",
|
|
106
|
+
"grant_lost",
|
|
107
|
+
]);
|
|
108
|
+
expect(
|
|
109
|
+
ScheduleReproveResponseSchema.safeParse({
|
|
110
|
+
outcome: "refused",
|
|
111
|
+
executionId: "x/2026-10-12T15:00:00Z",
|
|
112
|
+
refusal: "not_exhausted",
|
|
113
|
+
}).success,
|
|
114
|
+
).toBe(true);
|
|
115
|
+
expect(
|
|
116
|
+
ScheduleReproveResponseSchema.safeParse({
|
|
117
|
+
outcome: "refused",
|
|
118
|
+
executionId: "x/2026-10-12T15:00:00Z",
|
|
119
|
+
refusal: "because",
|
|
120
|
+
}).success,
|
|
121
|
+
).toBe(false);
|
|
122
|
+
});
|
|
72
123
|
});
|
|
@@ -19,6 +19,9 @@ describe("schedules exports", () => {
|
|
|
19
19
|
"ScheduleClaimResponseSchema",
|
|
20
20
|
"SCHEDULE_TICK_PATH",
|
|
21
21
|
"SCHEDULE_METRICS_NAMESPACE",
|
|
22
|
+
"SCHEDULE_REPROVE_PATH",
|
|
23
|
+
"SCHEDULE_REPROVE_REFUSALS",
|
|
24
|
+
"ScheduleReproveResponseSchema",
|
|
22
25
|
]) {
|
|
23
26
|
expect(root).toHaveProperty(name);
|
|
24
27
|
}
|
package/src/schedules/api.ts
CHANGED
|
@@ -137,3 +137,76 @@ export const ScheduleTickResponseSchema = z
|
|
|
137
137
|
})
|
|
138
138
|
.strict();
|
|
139
139
|
export type ScheduleTickResponse = z.infer<typeof ScheduleTickResponseSchema>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The operator re-prove route (control ADR slug operator-schedule-reprove).
|
|
143
|
+
* Its only caller is control's schedules-reprove.yml on main, authenticated by
|
|
144
|
+
* that job's GitHub OIDC token; an operator starts the workflow with
|
|
145
|
+
* `pnpm schedules:reprove <scheduleId> <slot> --reason "…"`. The route only
|
|
146
|
+
* GRANTS one extra attempt for a named execution; the backend clock dispatches
|
|
147
|
+
* it through the normal claim-before-call path.
|
|
148
|
+
*/
|
|
149
|
+
export const SCHEDULE_REPROVE_PATH = "/api/schedules/executions/reprove";
|
|
150
|
+
|
|
151
|
+
/** Upper bound of a re-prove reason (operator prose, kept for audit only). */
|
|
152
|
+
export const SCHEDULE_REPROVE_REASON_MAX_LENGTH = 500;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Re-prove request body, as a TYPE only: the backend binds its zod schema to it
|
|
156
|
+
* with `satisfies z.ZodType<ScheduleReproveRequest>`. `executionId` names the
|
|
157
|
+
* exhausted slot (`<schedule_id>/<slot>`); `reason` (1..500 chars) says why the
|
|
158
|
+
* operator believes the cause is fixed.
|
|
159
|
+
*/
|
|
160
|
+
export interface ScheduleReproveRequest {
|
|
161
|
+
readonly executionId: string;
|
|
162
|
+
readonly reason: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Why a re-prove was refused. Closed.
|
|
167
|
+
* - unknown_execution: no ledger row for that execution id.
|
|
168
|
+
* - schedule_not_live: not in the active registry, or its mode is not live.
|
|
169
|
+
* - not_exhausted: the clock still owns the slot (attempts remain and effects
|
|
170
|
+
* allow re-execution, a grant is unspent, or admission has not timed out).
|
|
171
|
+
* - not_reprovable: succeeded, settled, due, started, or failed only its
|
|
172
|
+
* output verification.
|
|
173
|
+
* - schedule_busy: another execution of the same schedule is in flight.
|
|
174
|
+
* - grant_lost: a concurrent grant or dispatch changed the row first.
|
|
175
|
+
*/
|
|
176
|
+
export const SCHEDULE_REPROVE_REFUSALS = [
|
|
177
|
+
"unknown_execution",
|
|
178
|
+
"schedule_not_live",
|
|
179
|
+
"not_exhausted",
|
|
180
|
+
"not_reprovable",
|
|
181
|
+
"schedule_busy",
|
|
182
|
+
"grant_lost",
|
|
183
|
+
] as const;
|
|
184
|
+
export type ScheduleReproveRefusal = (typeof SCHEDULE_REPROVE_REFUSALS)[number];
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* `granted`: the clock will dispatch `<executionId>#<grantedAttempt>` on its
|
|
188
|
+
* next pass. `incidentKey` is the open execution/admission incident naming the
|
|
189
|
+
* slot (null when none); `incidentNoted` says whether the grant was commented
|
|
190
|
+
* on its issue (a failed comment never revokes the grant).
|
|
191
|
+
*/
|
|
192
|
+
export const ScheduleReproveResponseSchema = z.discriminatedUnion("outcome", [
|
|
193
|
+
z
|
|
194
|
+
.object({
|
|
195
|
+
outcome: z.literal("granted"),
|
|
196
|
+
executionId: z.string(),
|
|
197
|
+
grantedAttempt: z.number().int().min(2),
|
|
198
|
+
incidentKey: z.string().nullable(),
|
|
199
|
+
incidentNoted: z.boolean(),
|
|
200
|
+
})
|
|
201
|
+
.strict(),
|
|
202
|
+
z
|
|
203
|
+
.object({
|
|
204
|
+
outcome: z.literal("refused"),
|
|
205
|
+
executionId: z.string(),
|
|
206
|
+
refusal: z.enum(SCHEDULE_REPROVE_REFUSALS),
|
|
207
|
+
})
|
|
208
|
+
.strict(),
|
|
209
|
+
]);
|
|
210
|
+
export type ScheduleReproveResponse = z.infer<
|
|
211
|
+
typeof ScheduleReproveResponseSchema
|
|
212
|
+
>;
|
package/src/schedules/index.ts
CHANGED
|
@@ -108,6 +108,10 @@ export {
|
|
|
108
108
|
ScheduleFinishResponseSchema,
|
|
109
109
|
ScheduleTickResponseSchema,
|
|
110
110
|
SCHEDULE_TICK_PATH,
|
|
111
|
+
SCHEDULE_REPROVE_PATH,
|
|
112
|
+
SCHEDULE_REPROVE_REASON_MAX_LENGTH,
|
|
113
|
+
SCHEDULE_REPROVE_REFUSALS,
|
|
114
|
+
ScheduleReproveResponseSchema,
|
|
111
115
|
SCHEDULE_METRICS_NAMESPACE,
|
|
112
116
|
SCHEDULE_METRIC_TICK,
|
|
113
117
|
SCHEDULE_METRIC_TICK_SOURCE_DIMENSION,
|
|
@@ -121,4 +125,7 @@ export type {
|
|
|
121
125
|
ScheduleFinishResponse,
|
|
122
126
|
ScheduleTickResponse,
|
|
123
127
|
ScheduleTickRequest,
|
|
128
|
+
ScheduleReproveRequest,
|
|
129
|
+
ScheduleReproveRefusal,
|
|
130
|
+
ScheduleReproveResponse,
|
|
124
131
|
} from "./api";
|
|
@@ -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`.
|