@company-semantics/contracts 100.0.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 +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/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
|
@@ -47,10 +47,13 @@ const PersonLabel = z.string().min(1).max(255);
|
|
|
47
47
|
* - An approved scope reports the routing its approval RECORDED; a later head
|
|
48
48
|
* change does not rewrite it. A pending scope reports its routing as of now.
|
|
49
49
|
* - `approvable` is whether THE CALLER may approve this pending scope now. A scope
|
|
50
|
-
* can be visible but not approvable: approving needs
|
|
51
|
-
*
|
|
52
|
-
* approver. `blockedReason` is non-null
|
|
53
|
-
*
|
|
50
|
+
* can be visible but not approvable: approving needs approval authority on the
|
|
51
|
+
* scope AND the caller's own upload and edit access on the scope's document,
|
|
52
|
+
* because landing and apply act as the approver. `blockedReason` is non-null
|
|
53
|
+
* exactly when a pending scope is not approvable BY THE CALLER:
|
|
54
|
+
* `needs_approval_authority` (no approval authority: e.g. an org admin, who
|
|
55
|
+
* coordinates and never approves) or `needs_doc_access`. An approved scope is
|
|
56
|
+
* never approvable and carries null. At the
|
|
54
57
|
* synthesis checkpoint a pending scope is approvable: apply is decided per
|
|
55
58
|
* document by the edit policy, which approving the candidate set required.
|
|
56
59
|
*/
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import { IsoDateTime } from "../api/primitives";
|
|
21
|
+
import { SOURCE_SYNC_SCOPE_BLOCKED_REASONS } from "./vocabulary";
|
|
21
22
|
|
|
22
23
|
export const SOURCE_SYNC_FINDING_KINDS = [
|
|
23
24
|
"source_changed",
|
|
@@ -92,6 +93,29 @@ export type SourceSyncAutoApplyCeiling =
|
|
|
92
93
|
|
|
93
94
|
const MicroUsd = z.string().regex(/^[0-9]+$/);
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Why a standing order stopped acting. Closed; mirrors the backend
|
|
98
|
+
* `source_sync_standing_orders_suspended_reason_valid` CHECK exactly.
|
|
99
|
+
*
|
|
100
|
+
* - `grantor_lost_authority`: the grantor no longer holds approval authority on
|
|
101
|
+
* the scope (they left the org, lost the unit, or were an org admin, who no
|
|
102
|
+
* longer approves).
|
|
103
|
+
* - `grantor_lost_doc_access`: the grantor still holds the authority but can no
|
|
104
|
+
* longer upload to and edit the scope's document, so an unattended landing or
|
|
105
|
+
* apply would be refused as them. Nothing is retried as anyone else.
|
|
106
|
+
* - `spend_cap_reached`: the month's spend met the cap the grantor stated.
|
|
107
|
+
* - `connection_lost`: Google Drive was disconnected; a reconnect re-activates
|
|
108
|
+
* nothing.
|
|
109
|
+
*/
|
|
110
|
+
export const SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS = [
|
|
111
|
+
"grantor_lost_authority",
|
|
112
|
+
"grantor_lost_doc_access",
|
|
113
|
+
"spend_cap_reached",
|
|
114
|
+
"connection_lost",
|
|
115
|
+
] as const;
|
|
116
|
+
export type SourceSyncStandingOrderSuspendedReason =
|
|
117
|
+
(typeof SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS)[number];
|
|
118
|
+
|
|
95
119
|
export const SourceSyncStandingOrderSchema = z.object({
|
|
96
120
|
/** 'company' or 'unit:<uuid>'. */
|
|
97
121
|
scopeKey: z.string().min(1).max(64),
|
|
@@ -101,14 +125,46 @@ export const SourceSyncStandingOrderSchema = z.object({
|
|
|
101
125
|
/** Display-only, resolved at read time. */
|
|
102
126
|
grantedByLabel: z.string().max(255).nullable(),
|
|
103
127
|
grantedAt: IsoDateTime,
|
|
104
|
-
/** Why it stopped acting
|
|
128
|
+
/** Why it stopped acting; null unless suspended. */
|
|
105
129
|
suspendedReason: z
|
|
106
|
-
.enum(
|
|
130
|
+
.enum(SOURCE_SYNC_STANDING_ORDER_SUSPENDED_REASONS)
|
|
107
131
|
.nullable(),
|
|
108
132
|
});
|
|
109
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Whether the CALLER may grant (turn on, change or turn on again) the standing
|
|
136
|
+
* order of one scope, decided by the server with exactly the rule a grant
|
|
137
|
+
* enforces: approval authority on the scope (an account owner, or authority on
|
|
138
|
+
* that unit) AND the caller's own upload and edit access on the scope's
|
|
139
|
+
* document. Authority is checked first: `needs_approval_authority` when the
|
|
140
|
+
* caller holds none (an org admin coordinates, never approves);
|
|
141
|
+
* `needs_doc_access` when they hold it but cannot act on the document.
|
|
142
|
+
* `blockedReason` is non-null exactly when `grantable` is false. Advisory: the
|
|
143
|
+
* grant re-decides and refuses (403) whatever this said.
|
|
144
|
+
*/
|
|
145
|
+
export const SourceSyncStandingOrderGrantabilitySchema = z
|
|
146
|
+
.object({
|
|
147
|
+
/** 'company' or 'unit:<uuid>'. */
|
|
148
|
+
scopeKey: z.string().min(1).max(64),
|
|
149
|
+
grantable: z.boolean(),
|
|
150
|
+
blockedReason: z.enum(SOURCE_SYNC_SCOPE_BLOCKED_REASONS).nullable(),
|
|
151
|
+
})
|
|
152
|
+
.strict()
|
|
153
|
+
.refine((g) => g.grantable === (g.blockedReason === null), {
|
|
154
|
+
message:
|
|
155
|
+
"blockedReason is non-null exactly when the scope is not grantable",
|
|
156
|
+
path: ["blockedReason"],
|
|
157
|
+
});
|
|
158
|
+
|
|
110
159
|
export const SourceSyncStandingOrdersResponseSchema = z.object({
|
|
111
160
|
orders: z.array(SourceSyncStandingOrderSchema).max(500),
|
|
161
|
+
/**
|
|
162
|
+
* The caller's grantability: the company scope first, then each distinct
|
|
163
|
+
* scope of an active or suspended order in `orders`, at most once each. A
|
|
164
|
+
* scope absent here is undecided (the server evaluated a bounded number); the
|
|
165
|
+
* grant still decides.
|
|
166
|
+
*/
|
|
167
|
+
grantability: z.array(SourceSyncStandingOrderGrantabilitySchema).max(500),
|
|
112
168
|
});
|
|
113
169
|
|
|
114
170
|
/**
|
|
@@ -132,6 +188,9 @@ export type SourceSyncDismissFindingRequest = z.infer<
|
|
|
132
188
|
export type SourceSyncStandingOrder = z.infer<
|
|
133
189
|
typeof SourceSyncStandingOrderSchema
|
|
134
190
|
>;
|
|
191
|
+
export type SourceSyncStandingOrderGrantability = z.infer<
|
|
192
|
+
typeof SourceSyncStandingOrderGrantabilitySchema
|
|
193
|
+
>;
|
|
135
194
|
export type SourceSyncStandingOrdersResponse = z.infer<
|
|
136
195
|
typeof SourceSyncStandingOrdersResponseSchema
|
|
137
196
|
>;
|
|
@@ -112,12 +112,26 @@ export type SourceSyncScopeApproverKind =
|
|
|
112
112
|
(typeof SOURCE_SYNC_SCOPE_APPROVER_KINDS)[number];
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
* Why the CALLER may not approve a scope they can see
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
115
|
+
* Why the CALLER may not approve a scope they can see, or grant its standing
|
|
116
|
+
* order. One vocabulary for both decisions (they are the same rule).
|
|
117
|
+
*
|
|
118
|
+
* - `needs_approval_authority`: the caller may see the scope but holds no
|
|
119
|
+
* approval authority on it. Approval authority is an account owner, or
|
|
120
|
+
* authority on that unit (local, inherited or delegated). An org admin
|
|
121
|
+
* coordinates and never approves: being responsible for work getting done is
|
|
122
|
+
* not authority to do it.
|
|
123
|
+
* - `needs_doc_access`: the caller holds the authority but lacks their own
|
|
124
|
+
* upload and edit access on the scope's document; landing and apply act as
|
|
125
|
+
* the approver (or a standing order's grantor), so approving would land
|
|
126
|
+
* nothing.
|
|
127
|
+
*
|
|
128
|
+
* Authority is checked first, so a caller lacking both reads
|
|
129
|
+
* `needs_approval_authority`.
|
|
119
130
|
*/
|
|
120
|
-
export const SOURCE_SYNC_SCOPE_BLOCKED_REASONS = [
|
|
131
|
+
export const SOURCE_SYNC_SCOPE_BLOCKED_REASONS = [
|
|
132
|
+
"needs_approval_authority",
|
|
133
|
+
"needs_doc_access",
|
|
134
|
+
] as const;
|
|
121
135
|
export type SourceSyncScopeBlockedReason =
|
|
122
136
|
(typeof SOURCE_SYNC_SCOPE_BLOCKED_REASONS)[number];
|
|
123
137
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
|
|
14
14
|
SYNTHESIZE_AFFORDANCES_OF_REASON,
|
|
15
15
|
SYNTHESIZE_OUTCOME_KIND_OF_REASON,
|
|
16
|
+
SynthesizeOutcomeFactsSchema,
|
|
16
17
|
WORKFLOW_OUTCOME_KINDS,
|
|
17
18
|
} from "../outcome-reasons";
|
|
18
19
|
|
|
@@ -193,3 +194,83 @@ describe("WorkflowOutcome", () => {
|
|
|
193
194
|
).toBe(false);
|
|
194
195
|
});
|
|
195
196
|
});
|
|
197
|
+
|
|
198
|
+
describe("source-sync/synthesize unattended endings", () => {
|
|
199
|
+
// The strict member (src/workflows/outcome.ts memberSchema): workflowType,
|
|
200
|
+
// executionEpoch, kind, reason, facts, disposition, affordances. Each test
|
|
201
|
+
// below supplies kind, reason, facts and its `review` disposition.
|
|
202
|
+
const base = {
|
|
203
|
+
workflowType: "source-sync/synthesize",
|
|
204
|
+
executionEpoch: 0,
|
|
205
|
+
affordances: [],
|
|
206
|
+
};
|
|
207
|
+
const unattended = {
|
|
208
|
+
scopeCount: 1,
|
|
209
|
+
violationCount: 0,
|
|
210
|
+
notLandedCount: 0,
|
|
211
|
+
appliedDocCount: 1,
|
|
212
|
+
notAppliedDocCount: 1,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
it("a partly applied unattended batch is partial and may hand off to review", () => {
|
|
216
|
+
expect(SYNTHESIZE_OUTCOME_KIND_OF_REASON.updates_applied_partial).toBe(
|
|
217
|
+
"partial",
|
|
218
|
+
);
|
|
219
|
+
expect(SYNTHESIZE_OUTCOME_KIND_OF_REASON.apply_refused).toBe("partial");
|
|
220
|
+
expect(
|
|
221
|
+
WorkflowOutcomeSchema.safeParse({
|
|
222
|
+
...base,
|
|
223
|
+
kind: "partial",
|
|
224
|
+
reason: "updates_applied_partial",
|
|
225
|
+
facts: unattended,
|
|
226
|
+
disposition: {
|
|
227
|
+
kind: "review",
|
|
228
|
+
requestType: "source-sync/synthesis",
|
|
229
|
+
requestId: "batch-1",
|
|
230
|
+
},
|
|
231
|
+
}).success,
|
|
232
|
+
).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("an unattended ending names how many documents it applied", () => {
|
|
236
|
+
const { appliedDocCount: _a, ...withoutApplied } = unattended;
|
|
237
|
+
expect(
|
|
238
|
+
WorkflowOutcomeSchema.safeParse({
|
|
239
|
+
...base,
|
|
240
|
+
kind: "partial",
|
|
241
|
+
reason: "apply_refused",
|
|
242
|
+
facts: withoutApplied,
|
|
243
|
+
disposition: {
|
|
244
|
+
kind: "review",
|
|
245
|
+
requestType: "source-sync/synthesis",
|
|
246
|
+
requestId: "batch-1",
|
|
247
|
+
},
|
|
248
|
+
}).success,
|
|
249
|
+
).toBe(false);
|
|
250
|
+
expect(
|
|
251
|
+
WorkflowOutcomeSchema.safeParse({
|
|
252
|
+
...base,
|
|
253
|
+
kind: "partial",
|
|
254
|
+
reason: "apply_refused",
|
|
255
|
+
facts: { ...unattended, appliedDocCount: 0, notAppliedDocCount: 2 },
|
|
256
|
+
disposition: {
|
|
257
|
+
kind: "review",
|
|
258
|
+
requestType: "source-sync/synthesis",
|
|
259
|
+
requestId: "batch-1",
|
|
260
|
+
},
|
|
261
|
+
}).success,
|
|
262
|
+
).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("an attended ending says no automatic apply happened with null counts", () => {
|
|
266
|
+
expect(
|
|
267
|
+
SynthesizeOutcomeFactsSchema.safeParse({
|
|
268
|
+
scopeCount: 1,
|
|
269
|
+
violationCount: 0,
|
|
270
|
+
notLandedCount: 0,
|
|
271
|
+
appliedDocCount: null,
|
|
272
|
+
notAppliedDocCount: null,
|
|
273
|
+
}).success,
|
|
274
|
+
).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -150,6 +150,10 @@ export const SYNTHESIZE_DEFERRED_ROUTE_OF_REASON = {
|
|
|
150
150
|
updates_ready_partial: { route: "covered", by: "sourceSync.review_ready", fallback: "notice", audience: "creator" },
|
|
151
151
|
// prettier-ignore
|
|
152
152
|
updates_applied: { route: "covered", by: "sourceSync.docs_updated", fallback: "notice", audience: "creator" },
|
|
153
|
+
// prettier-ignore
|
|
154
|
+
updates_applied_partial: { route: "covered", by: "sourceSync.review_ready", fallback: "notice", audience: "creator" },
|
|
155
|
+
// prettier-ignore
|
|
156
|
+
apply_refused: { route: "covered", by: "sourceSync.review_ready", fallback: "notice", audience: "creator" },
|
|
153
157
|
stopped_by_user: STOPPED,
|
|
154
158
|
paused_billing: EXPLAINED,
|
|
155
159
|
run_ceiling_reached: NOTICE_ADMINS,
|
|
@@ -122,7 +122,19 @@ export const SYNTHESIZE_OUTCOME_KIND_OF_REASON = {
|
|
|
122
122
|
updates_ready: "produced",
|
|
123
123
|
/** Drafted from what landed; some approved files could not be brought in. */
|
|
124
124
|
updates_ready_partial: "partial",
|
|
125
|
+
/** An unattended batch: the standing order applied EVERY drafted document. */
|
|
125
126
|
updates_applied: "produced",
|
|
127
|
+
/**
|
|
128
|
+
* An unattended batch: the standing order applied some documents; the rest
|
|
129
|
+
* (proposed by the auto-apply gate, or refused when applied) wait for a person.
|
|
130
|
+
*/
|
|
131
|
+
updates_applied_partial: "partial",
|
|
132
|
+
/**
|
|
133
|
+
* An unattended batch: the automatic apply was attempted and refused for every
|
|
134
|
+
* document it tried, so nothing was applied; the drafts wait for review. Never
|
|
135
|
+
* narrated as applied.
|
|
136
|
+
*/
|
|
137
|
+
apply_refused: "partial",
|
|
126
138
|
stopped_by_user: "stopped",
|
|
127
139
|
paused_billing: "failed",
|
|
128
140
|
run_ceiling_reached: "failed",
|
|
@@ -146,6 +158,8 @@ export const SYNTHESIZE_AFFORDANCES_OF_REASON = {
|
|
|
146
158
|
updates_ready: [],
|
|
147
159
|
updates_ready_partial: [],
|
|
148
160
|
updates_applied: [],
|
|
161
|
+
updates_applied_partial: [],
|
|
162
|
+
apply_refused: [],
|
|
149
163
|
stopped_by_user: [],
|
|
150
164
|
paused_billing: [OPEN_BILLING],
|
|
151
165
|
run_ceiling_reached: [],
|
|
@@ -173,6 +187,18 @@ export const SynthesizeOutcomeFactsSchema = z
|
|
|
173
187
|
violationCount: Count,
|
|
174
188
|
/** Approved files to attach that did not land (refused, unreadable, gone). */
|
|
175
189
|
notLandedCount: Count,
|
|
190
|
+
/**
|
|
191
|
+
* Documents an UNATTENDED batch applied on its own (the standing order's
|
|
192
|
+
* auto-apply). Null when no automatic apply happened at all: an attended
|
|
193
|
+
* run, or an unattended run that ended before its batch validated. Missing
|
|
194
|
+
* evidence is null, never 0.
|
|
195
|
+
*/
|
|
196
|
+
appliedDocCount: Count.nullable(),
|
|
197
|
+
/**
|
|
198
|
+
* Documents of an UNATTENDED batch left for a person (proposed by the gate,
|
|
199
|
+
* or refused when applied). Null exactly when `appliedDocCount` is null.
|
|
200
|
+
*/
|
|
201
|
+
notAppliedDocCount: Count.nullable(),
|
|
176
202
|
})
|
|
177
203
|
.strict();
|
|
178
204
|
|