@instructure/platform-cohorts 0.2.0 → 0.3.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.
Files changed (2) hide show
  1. package/README.md +86 -0
  2. package/package.json +3 -1
package/README.md CHANGED
@@ -28,3 +28,89 @@ pnpm --filter @instructure/platform-cohorts build # vite lib build + .d.ts
28
28
  pnpm --filter @instructure/platform-cohorts type-check
29
29
  pnpm lint # biome + oxlint + ast-grep
30
30
  ```
31
+
32
+ ## The contract
33
+
34
+ `openapi.yaml` is OpenAPI 3.1, 22 operations across 14 paths, with no
35
+ server implementing it yet. `src/__tests__/openapi.test.ts` proves it's
36
+ structurally complete: the document validates against the OpenAPI 3.1
37
+ schema and every `$ref` resolves, every operation has a unique
38
+ `operationId`, a `403`, and a `404`, every `POST`/`PUT`/`DELETE` also has a
39
+ `400`, the path+method matrix matches exactly, the `Cohort`,
40
+ `WorkflowState`, and `ErrorCode` schemas carry the fields the rest of the
41
+ contract depends on, no path reaches into pseudonym, generator, or sync
42
+ territory, and no request body accepts a full user object where an id
43
+ would do. If a check fails, the fix belongs in the YAML, not the test.
44
+
45
+ The contract uses no custom (`x-*`) vendor extensions. Service limits live in
46
+ the schema and response descriptions that enforce them, and which operations
47
+ extend an existing Canvas API is documented in each operation's own
48
+ description.
49
+
50
+ Which permission an operation requires and which feature flag gates it are
51
+ determined by Canvas's real permission registry and feature-flag system,
52
+ not documented per-operation in this contract.
53
+
54
+ ### Conventions consumers rely on
55
+
56
+ - **Pagination**: list operations return a `Link` header in RFC 5988 format,
57
+ matching the rest of the Canvas API.
58
+ - **Ids**: every id is a 64-bit integer, modelled as the shared `Id` schema.
59
+ Canvas serialises ids as JSON strings instead when the request carries
60
+ `Accept: application/json+canvas-string-ids`, so clients should accept
61
+ both forms.
62
+ - **Error payloads**: `Error` (message + optional machine-readable
63
+ `error_code`), `ValidationError` (per-attribute field errors), and
64
+ `UnauthorizedError` (the `403` shape) cover every failure case. The one
65
+ exception is `bulkEnroll`, whose pre-existing Canvas validation keeps its
66
+ string-bodied `LegacyStringError`.
67
+ - **Status codes follow Canvas**: creates return `200`, not `201`; an
68
+ authenticated caller without permission gets `403` with
69
+ `status: unauthorized`. No operation in this contract returns `409`.
70
+ - **List filters follow Canvas**: lifecycle filtering is the repeatable
71
+ `workflow_state[]` (`active`, `deleted`, `all`), `per_page` caps at 100,
72
+ and `search_term` needs at least two characters.
73
+ - **404 means three things on purpose**: a missing/deleted resource, the
74
+ `institutional_cohorts` flag being off for the root account, or the
75
+ resource belonging to a different root account than the caller's. The API
76
+ never distinguishes these, so it never reveals what it won't show you.
77
+ - **400 `feature_disabled`**: the three extended Canvas operations
78
+ (`bulkEnroll`, `createAccountNotification`, `updateAccountNotification`)
79
+ keep working when the `institutional_cohorts` flag is off, but reject their
80
+ cohort-specific parameters with `400` and error code `feature_disabled`,
81
+ rather than 404ing the whole operation.
82
+ - **Form encodings**: the three extended Canvas operations accept
83
+ `application/x-www-form-urlencoded` and `multipart/form-data` with the
84
+ same schema. `canvas-fetch` emits `FormData` natively, so multipart is the
85
+ path of least resistance for clients in this repo.
86
+
87
+ ### Key contract decisions
88
+
89
+ - `createCohort` creates both kinds of cohort: pass `parent_cohort_id` for a
90
+ sub-cohort, or `null` for a root cohort at depth 1 with no parent. An
91
+ account may anchor several independent root cohorts — Canvas has no
92
+ `/root` route and neither does this contract. `listCohorts` finds them
93
+ with `roots=true`.
94
+ - Depth is 1-based (the root cohort is depth 1) and capped at 10.
95
+ - Deletion is soft: cohorts, memberships, and leader assignments move to
96
+ `workflow_state: deleted` rather than being removed.
97
+ - A Leader's rights always cascade to every cohort beneath the one the
98
+ assignment lives on — there's no way to scope a Leader to a single level.
99
+ - Adding a leader is idempotent: a duplicate `addCohortLeader` returns the
100
+ existing assignment with `200`, the way the Canvas admins API does.
101
+ - Leader grants carry no ceiling on which roles a leader may hand out. An
102
+ earlier revision refused, with `403`, any role other than the one that
103
+ authorised the caller. That rule appeared nowhere in the tech spec, so it
104
+ was dropped; Canvas instead enforces a subset rule on `AccountUser`
105
+ (`is_subset_of?`: the granted role's permissions must be a subset of the
106
+ caller's). If a ceiling is wanted, amend the spec first, and prefer the
107
+ Canvas subset rule over strict equality.
108
+ - `updateCohortLeader` changes a leader's role in place. The tech spec does
109
+ not describe it and the Canvas admins API has no equivalent (role change is
110
+ delete plus create there), so it stays pending a spec amendment.
111
+ - `getCohortTree` defaults to `depth=2` (the cohort and its direct children)
112
+ so a client expands one level at a time unless it asks for more; `depth=10`
113
+ returns the full hierarchy.
114
+ - Bulk course enrolment, program enrolment, and collection enrolment are all
115
+ one-shot actions: they resolve cohort membership once, when the job runs,
116
+ and nothing keeps the target roster in sync with the cohort afterwards.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/platform-cohorts",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -27,6 +27,7 @@
27
27
  "devDependencies": {
28
28
  "@instructure/ui-text": "11.7.4",
29
29
  "@instructure/ui-view": "11.7.4",
30
+ "@seriousme/openapi-schema-validator": "^2.9.1",
30
31
  "@storybook/react": "^10.0.8",
31
32
  "@tanstack/react-query": "^5.59.15",
32
33
  "@testing-library/jest-dom": "^6.9.1",
@@ -41,6 +42,7 @@
41
42
  "vite": "^6.0.0",
42
43
  "vite-plugin-dts": "^4.0.0",
43
44
  "vitest": "^4.0.0",
45
+ "yaml": "^2.8.2",
44
46
  "@instructure/platform-provider": "0.5.2",
45
47
  "@instructure/platform-test-utils": "0.2.5"
46
48
  },