@classytic/arc 2.11.4 → 2.13.1
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/README.md +16 -12
- package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
- package/dist/EventTransport-CT_52aWU.d.mts +34 -0
- package/dist/EventTransport-DLWoUMHy.mjs +103 -0
- package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/audit.d.mts +199 -0
- package/dist/auth/audit.mjs +288 -0
- package/dist/auth/index.d.mts +3 -3
- package/dist/auth/index.mjs +117 -191
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/describe.d.mts +89 -13
- package/dist/cli/commands/describe.mjs +56 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +147 -48
- package/dist/cli/commands/init.d.mts +13 -0
- package/dist/cli/commands/init.mjs +130 -87
- package/dist/cli/commands/introspect.mjs +8 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/core-D72ia0EH.mjs +1399 -0
- package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
- package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
- package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
- package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
- package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
- package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
- package/dist/errors-j4aJm1Wg.mjs +184 -0
- package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
- package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
- package/dist/events/index.d.mts +6 -6
- package/dist/events/index.mjs +11 -35
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
- package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
- package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +9 -10
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +60 -11
- package/dist/integrations/streamline.mjs +75 -85
- package/dist/integrations/websocket.mjs +2 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/migrations/index.d.mts +23 -3
- package/dist/migrations/index.mjs +0 -7
- package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
- package/dist/{openapi-D7G1V7ex.mjs → openapi-CiOMVW1p.mjs} +143 -13
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
- package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +16 -31
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +6 -9
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +6 -8
- package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
- package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-C5coh64w.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
- package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
- package/dist/schemas/index.d.mts +100 -30
- package/dist/schemas/index.mjs +86 -29
- package/dist/scim/index.d.mts +264 -0
- package/dist/scim/index.mjs +963 -0
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +4 -4
- package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
- package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -8
- package/dist/testing/index.mjs +16 -24
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
- package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
- package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +147 -51
- package/skills/arc/references/agent-auth.md +238 -0
- package/skills/arc/references/api-reference.md +187 -0
- package/skills/arc/references/auth.md +354 -7
- package/skills/arc/references/enterprise-auth.md +94 -0
- package/skills/arc/references/events.md +8 -6
- package/skills/arc/references/mcp.md +2 -2
- package/skills/arc/references/multi-tenancy.md +11 -2
- package/skills/arc/references/production.md +10 -9
- package/skills/arc/references/scim.md +247 -0
- package/skills/arc/references/testing.md +1 -1
- package/skills/arc-code-review/SKILL.md +141 -0
- package/skills/arc-code-review/references/anti-patterns.md +911 -0
- package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
- package/skills/arc-code-review/references/migration-recipes.md +700 -0
- package/skills/arc-code-review/references/mongokit-migration.md +386 -0
- package/skills/arc-code-review/references/scaffolding.md +230 -0
- package/skills/arc-code-review/references/severity.md +127 -0
- package/dist/EventTransport-BFQjw9pB.mjs +0 -133
- package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-DUUiiimH.mjs +0 -964
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-CbcQRIch.mjs +0 -1054
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-Rg8axYPz.d.mts +0 -370
- /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
- /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
- /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
- /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
- /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
- /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
- /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
- /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
- /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
- /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
- /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# SCIM 2.0 — IdP Provisioning
|
|
2
|
+
|
|
3
|
+
Arc ships a SCIM 2.0 plugin (`@classytic/arc/scim`) that auto-mounts `/scim/v2/Users` + `/scim/v2/Groups` REST endpoints and translates SCIM wire shapes onto the canonical **repository contract** (`@classytic/repo-core/adapter`). Arc does NOT introduce a SCIM-specific repository subset, controller pipeline, or storage tier — it composes with what kits already expose.
|
|
4
|
+
|
|
5
|
+
## Honest architecture (what SCIM actually is)
|
|
6
|
+
|
|
7
|
+
**SCIM is a thin REST layer over the kit's `RepositoryLike`.** Whatever plugins you wire at the kit/repo layer (audit, multi-tenant, field-policy, etc.) fire for SCIM exactly the way they fire for arc REST, because **both surfaces call the same repository methods**.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
SCIM request → arc/scim plugin → resource.adapter.repository.<method> → kit hooks fire
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This is NOT what some earlier docs implied. SCIM does not run arc's HTTP controller pipeline (`auth → permissions → preHandlers → controller → hooks → audit`). SCIM authentication is bearer/verify only at the REST edge; everything else (row-level perms, audit trail, hooks) is composed at the kit layer where it already lives.
|
|
14
|
+
|
|
15
|
+
| Layer | Where it composes | Fires for SCIM? |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Bearer / OIDC verify | `scimPlugin({ bearer, verify })` | ✓ at the SCIM edge |
|
|
18
|
+
| Audit trail | `repo.use(auditTrailPlugin())` (mongokit) — kit-specific identifier | ✓ via repo hooks |
|
|
19
|
+
| Multi-tenant scope | `repo.use(multiTenantPlugin)` | ✓ via repo hooks |
|
|
20
|
+
| Field redaction (read) | mongokit's `fieldFilterPlugin` | ✓ on `getAll` / `getById` |
|
|
21
|
+
| Resource hooks | `defineResource({ hooks: { ... } })` | ✗ — those are HTTP-controller hooks; not on the repo path |
|
|
22
|
+
| Per-action permissions | `defineResource({ permissions: { ... } })` | ✗ — same reason; gate at the edge via `verify` |
|
|
23
|
+
|
|
24
|
+
If you want resource hooks to fire for SCIM writes too, install them as **kit plugins** (`repo.on('before:create', fn)`) rather than `defineResource({ hooks: ... })`. The docs for your kit (mongokit / sqlitekit) cover the hook surface.
|
|
25
|
+
|
|
26
|
+
## CRUD → repository contract mapping
|
|
27
|
+
|
|
28
|
+
| SCIM method | Repository call | Notes |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `GET /Users` | `repo.getAll({ filters, page, limit, sort })` | Filter parser maps SCIM filter language → query DSL |
|
|
31
|
+
| `GET /Users/:id` | `repo.getById(id)` | |
|
|
32
|
+
| `POST /Users` | `repo.create(data)` | Inbound SCIM body maps onto resource shape via `mapping.attributes` |
|
|
33
|
+
| `PUT /Users/:id` | `repo.bulkWrite([{ replaceOne: { filter, replacement } }])` | **Kit-conditional** — see "Feature gating" below |
|
|
34
|
+
| `PATCH /Users/:id` | `repo.findOneAndUpdate({ id }, ops)` | Operators flow through unchanged (`$set`, `$unset`, `$push`, `$pull`) |
|
|
35
|
+
| `DELETE /Users/:id` | `repo.delete(id)` | |
|
|
36
|
+
|
|
37
|
+
## Feature gating (honest about what each kit supports)
|
|
38
|
+
|
|
39
|
+
**SCIM PATCH** translates the RFC 7644 PatchOp body into canonical Mongo-style operators and forwards them to `findOneAndUpdate`. The kit decides what's portable:
|
|
40
|
+
|
|
41
|
+
| Kit | `$set` / scalar overwrite | `$unset` | `$push` / `$pull` (array mutations) |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| **mongokit** | ✓ | ✓ | ✓ — applied natively |
|
|
44
|
+
| **sqlitekit** | ✓ (compiled to flat column writes) | ✓ | ✗ — JSON columns have no array-op semantics; sqlitekit throws cleanly, SCIM 400s with `scimType: invalidValue` |
|
|
45
|
+
| **prismakit** | ✓ | ✓ | depends on column type |
|
|
46
|
+
| **Custom kit (only `MinimalRepo`)** | ✓ via `update(id, $set)` fallback | ✗ — no `findOneAndUpdate` exposed → SCIM 400 | ✗ → SCIM 400 |
|
|
47
|
+
|
|
48
|
+
**SCIM PUT** requires `repo.bulkWrite([{ replaceOne }])` OR a top-level `repo.replace(id, doc)`. Kits that expose neither return **HTTP 501** with a clear message — no silent merge into `update(id, partial)` (which would leave omitted fields surviving and violate SCIM PUT semantics).
|
|
49
|
+
|
|
50
|
+
| Kit | `PUT` (full replace) |
|
|
51
|
+
|---|---|
|
|
52
|
+
| **mongokit** | ✓ via `bulkWrite([{ replaceOne }])` |
|
|
53
|
+
| **sqlitekit** | ✓ via `bulkWrite([{ replaceOne }])` AND `replace(id, doc)` (sqlitekit ≥0.4.0). Routes through `replaceById` (UPDATE with explicit NULLs for omitted columns) so SCIM PUT semantics are honored — omitted fields don't survive. |
|
|
54
|
+
| **prismakit** | varies |
|
|
55
|
+
| **Custom kit (only `MinimalRepo`)** | ✗ — 501 |
|
|
56
|
+
|
|
57
|
+
If your IdP requires PUT semantics and your kit doesn't expose `bulkWrite`, two options: (1) ask the kit team to add it, (2) configure your IdP to use PATCH instead (Okta / Azure AD both support PATCH-only flows).
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { scimPlugin } from "@classytic/arc/scim";
|
|
63
|
+
|
|
64
|
+
await app.register(scimPlugin, {
|
|
65
|
+
users: { resource: userResource }, // your existing arc resource
|
|
66
|
+
groups: { resource: orgResource }, // optional
|
|
67
|
+
bearer: process.env.SCIM_TOKEN, // OR: verify: async (req) => …
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That's it. Endpoints mounted: `GET/POST/PUT/PATCH/DELETE /scim/v2/Users[/:id]`, same for `Groups`, plus `ServiceProviderConfig` / `ResourceTypes` / `Schemas` discovery.
|
|
72
|
+
|
|
73
|
+
## Default mapping (Better Auth aligned)
|
|
74
|
+
|
|
75
|
+
If you don't override `mapping`, SCIM assumes the BA `user` / `organization` schema:
|
|
76
|
+
|
|
77
|
+
| SCIM attribute | Backend field |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `id` | `id` |
|
|
80
|
+
| `userName` | `email` |
|
|
81
|
+
| `name.formatted` / `displayName` | `name` |
|
|
82
|
+
| `emails[].value` | `email` (primary) |
|
|
83
|
+
| `active` | `isActive` |
|
|
84
|
+
| `externalId` | `externalId` |
|
|
85
|
+
| `meta.created` | `createdAt` |
|
|
86
|
+
| `meta.lastModified` | `updatedAt` |
|
|
87
|
+
|
|
88
|
+
For non-BA schemas, override per-attribute:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { scimPlugin, DEFAULT_USER_MAPPING } from "@classytic/arc/scim";
|
|
92
|
+
|
|
93
|
+
await app.register(scimPlugin, {
|
|
94
|
+
users: {
|
|
95
|
+
resource: userResource,
|
|
96
|
+
mapping: {
|
|
97
|
+
attributes: {
|
|
98
|
+
...DEFAULT_USER_MAPPING.attributes,
|
|
99
|
+
userName: "username",
|
|
100
|
+
"name.familyName": "lastName",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
bearer: process.env.SCIM_TOKEN,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Filter language (RFC 7644 §3.4.2.2)
|
|
109
|
+
|
|
110
|
+
Operators supported: `eq`, `ne`, `co` (contains), `sw` (starts with), `ew` (ends with), `gt`/`ge`/`lt`/`le`, `pr` (present), `and` / `or` / `not`, grouped with `( )`.
|
|
111
|
+
|
|
112
|
+
Real production filters that work out of the box:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
filter=userName eq "alice@acme.com" and active eq true
|
|
116
|
+
filter=externalId eq "ad:f3e9-..."
|
|
117
|
+
filter=meta.lastModified gt "2025-01-01T00:00:00Z"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Auth — bearer or verify
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
bearer: process.env.SCIM_TOKEN, // simplest
|
|
124
|
+
// OR
|
|
125
|
+
verify: async (request) => {
|
|
126
|
+
const auth = request.headers.authorization;
|
|
127
|
+
if (!auth?.startsWith("Bearer ")) return false;
|
|
128
|
+
const claims = await verifyJwt(auth.slice(7));
|
|
129
|
+
return claims.scope?.includes("scim:write") ?? false;
|
|
130
|
+
},
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Pass exactly one — `bearer` XOR `verify`. The plugin throws at boot if both / neither are configured.
|
|
134
|
+
|
|
135
|
+
## Observability
|
|
136
|
+
|
|
137
|
+
Every request emits one `ScimObservedEvent`:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
{
|
|
141
|
+
resourceType: "Users" | "Groups" | "discovery",
|
|
142
|
+
op: "list" | "get" | "create" | "replace" | "patch" | "delete" | "discovery.<endpoint>",
|
|
143
|
+
status: number,
|
|
144
|
+
durationMs: number,
|
|
145
|
+
scimType?: string, // SCIM error type when failed
|
|
146
|
+
path: string,
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Wire to your metrics / logging stack via `observe`:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
await app.register(scimPlugin, {
|
|
154
|
+
users: { resource: userResource },
|
|
155
|
+
bearer: process.env.SCIM_TOKEN,
|
|
156
|
+
observe: (event) => {
|
|
157
|
+
metrics.histogram("scim.duration_ms", event.durationMs, {
|
|
158
|
+
op: event.op,
|
|
159
|
+
status: String(event.status),
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Default (when `observe` is omitted): `request.log.info({ scim: event }, "scim.request")` — Pino-friendly structured log line.
|
|
166
|
+
|
|
167
|
+
## Discovery endpoints
|
|
168
|
+
|
|
169
|
+
Auto-mounted; every IdP probes them during connector setup:
|
|
170
|
+
|
|
171
|
+
- `/scim/v2/ServiceProviderConfig` — capability advertisement (`patch.supported: true`, `bulk.supported: false`, `oauthbearertoken` auth)
|
|
172
|
+
- `/scim/v2/ResourceTypes` — `User` (always) + `Group` (when `groups` binding present)
|
|
173
|
+
- `/scim/v2/Schemas` — id + name stub (most IdPs treat as a sanity check)
|
|
174
|
+
|
|
175
|
+
## Error envelope (RFC 7644 §3.12)
|
|
176
|
+
|
|
177
|
+
Every error response uses the canonical SCIM shape:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
|
182
|
+
"status": "400",
|
|
183
|
+
"scimType": "invalidFilter",
|
|
184
|
+
"detail": "Attribute 'xyz' is not filterable"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Content-Type: `application/scim+json` on every response. Parser auto-registered, idempotent on redeclare.
|
|
189
|
+
|
|
190
|
+
## What's NOT supported (yet)
|
|
191
|
+
|
|
192
|
+
- **Bulk operations** (`/Bulk`) — most IdPs don't use them; discovery advertises `bulk.supported: false`
|
|
193
|
+
- **EnterpriseUser extension** (`employeeNumber`, `manager`, `costCenter`) — pass-through via `mapping.attributes` works today; first-class extension support lands when a paying customer asks
|
|
194
|
+
- **Schema introspection beyond IDs** — `/Schemas` returns id+name only
|
|
195
|
+
|
|
196
|
+
## Production checklist
|
|
197
|
+
|
|
198
|
+
- [ ] Mount on the same host as your REST surface (no separate SCIM service)
|
|
199
|
+
- [ ] Rotate `SCIM_TOKEN` quarterly; use `verify` callback with short-lived JWTs for multi-IdP setups
|
|
200
|
+
- [ ] Wire your kit's audit plugin at the **kit** layer (mongokit: `repo.use(auditTrailPlugin())` from `@classytic/mongokit/plugins`), not via `defineResource({ audit: true })` — only the kit-layer plugin fires for SCIM writes
|
|
201
|
+
- [ ] Test the Okta / Azure AD connector against `playground/enterprise-auth/` before production cutover
|
|
202
|
+
- [ ] Confirm your kit exposes `findOneAndUpdate` (for PATCH operators) and `bulkWrite` (for PUT) — see "Feature gating" above; otherwise pick the IdP flows your kit supports
|
|
203
|
+
|
|
204
|
+
## sqlitekit gap message — RESOLVED in sqlitekit ≥0.4.0
|
|
205
|
+
|
|
206
|
+
sqlitekit ≥0.4.0 ships both asks. The original message is preserved below for historical context.
|
|
207
|
+
|
|
208
|
+
**Status of each ask:**
|
|
209
|
+
|
|
210
|
+
1. **`bulkWrite([{ replaceOne }])` with full-replace semantics — ✓ shipped.** Routes through `actions/update.replaceById` which UPDATE-with-explicit-NULLs every column omitted from the replacement. Pinned by `tests/integration/replace-and-array-ops.test.ts`. The earlier behavior (silent partial-update) is fixed; SCIM PUT clients now see the contract honored.
|
|
211
|
+
2. **JSON-column array policy — ✓ shipped (option a, refuse cleanly).** `findOneAndUpdate` and `updateMany` throw a clear actionable error on `$push` / `$pull` / `$addToSet` / `$pop` / `$pullAll`, mirroring the refusal `claim()` already shipped. Arc's SCIM plugin translates the throw into `400 Bad Request` with `scimType: invalidValue`.
|
|
212
|
+
3. **Top-level `replace(id, doc)` — ✓ shipped.** Available alongside `bulkWrite([{ replaceOne }])`; arc can feature-detect either.
|
|
213
|
+
|
|
214
|
+
**Bonus fix:** `findOneAndUpdate` and `updateMany` now also accept raw mongo `$set` / `$unset` / `$inc` / `$setOnInsert` operator records (compiled to flat column writes via the existing `UpdateSpec` path). Previously these silently produced `near "where": syntax error` from Drizzle. SCIM PATCH operator forwarding now works on sqlitekit without an arc-side translation step.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### Original gap message (historical)
|
|
219
|
+
|
|
220
|
+
If your sqlitekit-backed app needs SCIM PUT (full replace) or PATCH array ops (`$push`/`$pull`), the underlying repository needs to expose two ops it currently doesn't:
|
|
221
|
+
|
|
222
|
+
> **From:** arc 2.13 SCIM consumer
|
|
223
|
+
>
|
|
224
|
+
> **Subject:** `bulkWrite` + JSON-column array operators for SCIM 2.0 PUT / PATCH
|
|
225
|
+
>
|
|
226
|
+
> **Context.** Arc 2.13 ships a SCIM 2.0 plugin that translates IdP requests onto the canonical `@classytic/repo-core/adapter` `RepositoryLike` contract. PATCH uses `findOneAndUpdate(filter, ops)` so operators (`$set` / `$unset` / `$push` / `$pull`) flow through unchanged. PUT uses `bulkWrite([{ replaceOne }])` because full-document replace isn't in `MinimalRepo`. mongokit covers both today; sqlitekit doesn't.
|
|
227
|
+
>
|
|
228
|
+
> **Asks (in priority order):**
|
|
229
|
+
>
|
|
230
|
+
> 1. **`bulkWrite([...])` with `replaceOne` support.** SCIM PUT (full replace) currently 501s on sqlitekit. The contract: `replaceOne: { filter, replacement }` should DELETE then INSERT (or UPDATE all columns) atomically. Other op types (`updateOne`, `deleteOne`) can stub or fan out to existing methods. Returning `{ matchedCount, modifiedCount }` is enough.
|
|
231
|
+
>
|
|
232
|
+
> 2. **Documented JSON-column array policy for `findOneAndUpdate`.** sqlitekit currently rejects `$push` / `$pull` on `text` (JSON) columns. Three reasonable options for the kit team to pick:
|
|
233
|
+
> - **(a)** Refuse cleanly (current behaviour) — sqlitekit throws; arc 400s with `scimType: invalidValue`. Honest. Document it.
|
|
234
|
+
> - **(b)** Read-modify-write helper (non-atomic). Reads the JSON, mutates, writes back. Document the non-atomicity for hosts to evaluate.
|
|
235
|
+
> - **(c)** SQLite `json_insert` / `json_remove` functions. Atomic via SQL; sqlite-version dependent.
|
|
236
|
+
>
|
|
237
|
+
> arc consumes whatever sqlitekit decides; the SCIM plugin doesn't depend on (b) or (c) being shipped.
|
|
238
|
+
>
|
|
239
|
+
> 3. **Optional: a `replace(id, doc)` top-level convenience.** If the team prefers not to expose `bulkWrite`, a kit-specific `replace(id, doc)` method on `SqliteRepository` would also unlock PUT — arc could feature-detect either path.
|
|
240
|
+
>
|
|
241
|
+
> **No urgency** — SCIM PUT can be substituted with PATCH for most IdPs (Okta / Azure AD support PATCH-only reconciliation modes). This is a "ship when natural" ask, not a blocker for any current consumer.
|
|
242
|
+
|
|
243
|
+
## See also
|
|
244
|
+
|
|
245
|
+
- [enterprise-auth.md](enterprise-auth.md) — feature matrix
|
|
246
|
+
- [agent-auth.md](agent-auth.md) — DPoP + capability mandates
|
|
247
|
+
- [`playground/enterprise-auth/`](../../../playground/enterprise-auth/) — runnable smoke
|
|
@@ -114,7 +114,7 @@ describe('Product resource — full coverage', () => {
|
|
|
114
114
|
## `expectArc(response)` — fluent envelope matchers
|
|
115
115
|
|
|
116
116
|
```typescript
|
|
117
|
-
expectArc(res).ok(); // 200/201
|
|
117
|
+
expectArc(res).ok(); // 200/201 with data envelope
|
|
118
118
|
expectArc(res).forbidden(); // 403, arc error envelope
|
|
119
119
|
expectArc(res).notFound().hasError(/not exist/);
|
|
120
120
|
expectArc(res).validationError().hasData({ fields: ['email'] });
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arc-code-review
|
|
3
|
+
description: |
|
|
4
|
+
Audit a client codebase that has @classytic/arc installed for gaps in arc-convention adoption.
|
|
5
|
+
Surfaces hand-rolled CRUD/auth/query/cache code that should be one defineResource() call,
|
|
6
|
+
Mongoose models that should use @classytic/mongokit, manual JSON Schema that should be fieldRules,
|
|
7
|
+
bypassed RequestScope, missing presets, and other patterns that defeat arc's "less code, more
|
|
8
|
+
maintainability" promise. Produces a prioritized migration report with before/after recipes.
|
|
9
|
+
Use when reviewing/auditing a downstream project that depends on @classytic/arc, when the
|
|
10
|
+
user asks for an "arc audit", "arc gap analysis", "arc migration plan", "why isn't arc helping
|
|
11
|
+
us", or when refactoring a Fastify/Express service to arc conventions.
|
|
12
|
+
Triggers: arc audit, arc review, arc gap, arc migration, arc convention check, arc compliance,
|
|
13
|
+
classytic audit, defineResource refactor, mongoose to mongokit, hand-rolled crud to arc,
|
|
14
|
+
arc adoption, arc lint, arc smell, arc anti-pattern.
|
|
15
|
+
license: MIT
|
|
16
|
+
metadata:
|
|
17
|
+
author: Classytic
|
|
18
|
+
tags:
|
|
19
|
+
- arc
|
|
20
|
+
- code-review
|
|
21
|
+
- audit
|
|
22
|
+
- migration
|
|
23
|
+
- refactor
|
|
24
|
+
- fastify
|
|
25
|
+
- mongoose
|
|
26
|
+
- mongokit
|
|
27
|
+
- convention-check
|
|
28
|
+
progressive_disclosure:
|
|
29
|
+
entry_point:
|
|
30
|
+
summary: "Audit a client codebase using @classytic/arc for unrealized convention gains. Detect hand-rolled CRUD/auth/query/cache, Mongoose-without-mongokit, bypassed scope, and emit a prioritized migration report."
|
|
31
|
+
when_to_use: "Reviewing or migrating a project that has arc installed but hasn't adopted defineResource()/presets/permissions/scope/mongokit. Use whenever the user asks 'why isn't arc helping us' or wants a gap analysis."
|
|
32
|
+
quick_start: "1. Confirm arc/mongokit versions 2. Run detection sweep (references/anti-patterns.md) 3. Score each finding (references/severity.md) 4. Emit report using template below"
|
|
33
|
+
context_limit: 900
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# arc-code-review
|
|
37
|
+
|
|
38
|
+
Audit skill for client projects that depend on `@classytic/arc`. Detects places where the team is writing code arc would have generated, then emits a prioritized migration plan with concrete before/after diffs.
|
|
39
|
+
|
|
40
|
+
## When to use
|
|
41
|
+
|
|
42
|
+
Invoke when:
|
|
43
|
+
- The user asks for an "arc audit", "arc gap analysis", "arc migration plan", or "why isn't arc helping us".
|
|
44
|
+
- A repo `dependsOn @classytic/arc` (or `@classytic/mongokit`) and the conversation is about refactoring, code-review, or onboarding.
|
|
45
|
+
- The user mentions hand-rolled CRUD, manual JSON Schema, raw `req.user` access, manual `Model.find()` in route handlers, or hand-written OpenAPI alongside arc.
|
|
46
|
+
- Migrating a Fastify/Express service to arc conventions.
|
|
47
|
+
|
|
48
|
+
**Do NOT use** for arc framework development itself — that's the `arc` skill in `skills/arc/`. This skill audits *consumers* of arc.
|
|
49
|
+
|
|
50
|
+
## Mental model — what arc replaces
|
|
51
|
+
|
|
52
|
+
One `defineResource()` call **replaces all of these** in a typical Fastify service:
|
|
53
|
+
|
|
54
|
+
| Hand-rolled today | Arc capability that subsumes it | Reference |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| 5 × `fastify.get/post/patch/delete` per resource | CRUD auto-generation | [arc-cheatsheet.md](references/arc-cheatsheet.md) |
|
|
57
|
+
| `if (req.user.role !== 'admin')` inside handler | `permissions: { create: requireRoles(['admin']) }` | [anti-patterns.md §4](references/anti-patterns.md) |
|
|
58
|
+
| Manual `req.query.filter` parsing, `$or`/`$and` building | `ArcQueryParser` / mongokit `QueryParser` | [anti-patterns.md §1](references/anti-patterns.md) |
|
|
59
|
+
| Hand-written `schema: { body, response }` per route | `schemaOptions.fieldRules` | [anti-patterns.md §2](references/anti-patterns.md) |
|
|
60
|
+
| `schema.set('toJSON', { transform })` to strip `password`/`__v` | `fieldRules: { password: { hidden: true } }` | [anti-patterns.md §5](references/anti-patterns.md) |
|
|
61
|
+
| Hand-maintained `openapi.yaml` / `swagger.json` | `arc docs ./openapi.json` | [anti-patterns.md §6](references/anti-patterns.md) |
|
|
62
|
+
| `eventBus.emit('product.created', ...)` in handler | `events: { created: {} }` (auto-emitted) | [anti-patterns.md §7](references/anti-patterns.md) |
|
|
63
|
+
| `cache.del('products-*')` after mutation | `cache: { tags: ['catalog'] }` (auto-invalidated) | [anti-patterns.md §8](references/anti-patterns.md) |
|
|
64
|
+
| `req.user._id`, `req.user.orgId` direct access | `getUserId(scope)`, `getOrgId(scope)` from `@classytic/arc/scope` | [anti-patterns.md §9](references/anti-patterns.md) |
|
|
65
|
+
| `import mongoose from 'mongoose'` in route/service files | Adapter-only via `createMongooseAdapter` | [anti-patterns.md §10](references/anti-patterns.md) |
|
|
66
|
+
| Soft-delete: `/deleted` route + `deletedAt` field + restore handler | `presets: ['softDelete']` | [anti-patterns.md §14](references/anti-patterns.md) |
|
|
67
|
+
| `class UserRepository { async create() { Model.create() } }` | `new Repository(Model)` (mongokit) | [mongokit-migration.md](references/mongokit-migration.md) |
|
|
68
|
+
| Per-schema `schema.pre('save', ...)` for timestamps/validation | `timestampPlugin()`, `validationChainPlugin()` | [mongokit-migration.md](references/mongokit-migration.md) |
|
|
69
|
+
| Hand-written `name === 'admin'` MCP tool handlers | `mcpPlugin({ resources })` (auto-generated) | [anti-patterns.md §15](references/anti-patterns.md) |
|
|
70
|
+
|
|
71
|
+
## Audit workflow
|
|
72
|
+
|
|
73
|
+
1. **Confirm arc is actually installed.** Read root `package.json`. Note `@classytic/arc`, `@classytic/mongokit`, `@classytic/repo-core`, `@classytic/sqlitekit` versions. If arc is absent, this skill doesn't apply — recommend `npx @classytic/arc init` instead.
|
|
74
|
+
2. **Locate the entry point.** Search for `createApp(` or `defineResource(` to see what arc surface is already in use. Note `auth`, `runtime`, `arcPlugins`, `presets` config.
|
|
75
|
+
3. **Inventory resources.** For each `defineResource()` call: list `name`, `permissions`, `presets`, `cache`, `schemaOptions`, custom `routes`/`actions`. Compare to what's used.
|
|
76
|
+
4. **Run the detection sweep.** Walk every section of [anti-patterns.md](references/anti-patterns.md). For each pattern, run the listed grep against `src/` (excluding `node_modules`, `dist`, `test*`). Record file:line of each hit.
|
|
77
|
+
5. **Score findings.** Apply [severity.md](references/severity.md) rubric (critical / high / medium / low). Critical = security gap or duplicated arc behavior that drifts. Low = cosmetic.
|
|
78
|
+
6. **Cross-check mongokit adoption.** If `mongoose` is a direct dep but `@classytic/mongokit` is not, every model is a candidate for migration. Use [mongokit-migration.md](references/mongokit-migration.md).
|
|
79
|
+
7. **Check arc CLI scaffolding hygiene.** Look for `.arcrc`, `arc generate resource` output structure (`{name}.model.ts`, `{name}.repository.ts`, `{name}.resource.ts`). Mismatch = team is hand-creating files. See [scaffolding.md](references/scaffolding.md).
|
|
80
|
+
8. **Emit the report** using the template below.
|
|
81
|
+
|
|
82
|
+
## Reporting template
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# Arc Convention Audit — <project-name>
|
|
86
|
+
|
|
87
|
+
**Arc version:** 3.0.x · **Mongokit:** <version or "not installed"> · **Sqlitekit:** <version or "n/a"> · **Date:** <YYYY-MM-DD>
|
|
88
|
+
**Files scanned:** <N> · **Findings:** <N critical · <N high · <N medium · <N low>
|
|
89
|
+
|
|
90
|
+
## Executive summary
|
|
91
|
+
- <1-2 sentences: how much manual code could be deleted, biggest single risk>
|
|
92
|
+
- Estimated LOC removable: ~<N> lines across <N> files
|
|
93
|
+
- Estimated effort: <S/M/L> per resource (<N> resources affected)
|
|
94
|
+
|
|
95
|
+
## Critical findings
|
|
96
|
+
### C1. <short title> (<N occurrences>)
|
|
97
|
+
**Pattern:** <what's wrong, in 1 sentence>
|
|
98
|
+
**Locations:** `src/foo/bar.ts:42`, `src/foo/baz.ts:118`, ...
|
|
99
|
+
**Why it matters:** <security / drift / maintainability impact>
|
|
100
|
+
**Fix:** <link to migration recipe>
|
|
101
|
+
|
|
102
|
+
## High / Medium / Low findings
|
|
103
|
+
(same shape)
|
|
104
|
+
|
|
105
|
+
## Migration plan (recommended order)
|
|
106
|
+
1. <Step 1 — usually scope/permissions because they're security-critical>
|
|
107
|
+
2. <Step 2 — usually CRUD consolidation>
|
|
108
|
+
3. ...
|
|
109
|
+
|
|
110
|
+
## Per-resource scorecard
|
|
111
|
+
| Resource | defineResource? | presets used | permissions | cache | events | mongokit | Score |
|
|
112
|
+
|---|---|---|---|---|---|---|---|
|
|
113
|
+
| product | ✅ | softDelete | ✅ | ❌ | manual | ❌ | 6/10 |
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Severity quick rule
|
|
117
|
+
|
|
118
|
+
- **Critical:** auth bypass, scope leak, fields exposed that should be `hidden`, hand-rolled idempotency that diverges from arc's behavior under load.
|
|
119
|
+
- **High:** duplicated CRUD that will rot (one resource gets a fix the others don't), manual permissions instead of combinators, mongoose imports leaking outside adapters.
|
|
120
|
+
- **Medium:** manual query parsing, manual cache invalidation, missing presets, hand-written OpenAPI.
|
|
121
|
+
- **Low:** style (default exports, `console.log`, `any`), naming conventions, missing `displayName`/`module` metadata.
|
|
122
|
+
|
|
123
|
+
Full rubric → [severity.md](references/severity.md).
|
|
124
|
+
|
|
125
|
+
## Output discipline
|
|
126
|
+
|
|
127
|
+
- **Cite file:line for every finding.** Do not generalize.
|
|
128
|
+
- **Show the before/after diff** for the first occurrence of each pattern. Subsequent occurrences just list locations.
|
|
129
|
+
- **Quote arc's exact API** in fixes (e.g., `requireRoles(['admin'])` from `@classytic/arc`, not "use arc's role helper").
|
|
130
|
+
- **Sort by severity, then by file count.** A pattern repeated 30× outranks an isolated critical bug for migration ROI.
|
|
131
|
+
- **Distinguish "missing arc feature" from "arc misuse".** The first is opportunity; the second is a bug.
|
|
132
|
+
- **Don't recommend arc features the project hasn't enabled.** If `arcPlugins.queryCache: false`, don't suggest cache tags — recommend enabling it first.
|
|
133
|
+
|
|
134
|
+
## References
|
|
135
|
+
|
|
136
|
+
- **[anti-patterns.md](references/anti-patterns.md)** — every greppable anti-pattern with detection regex, severity, and fix
|
|
137
|
+
- **[migration-recipes.md](references/migration-recipes.md)** — concrete before/after diffs (manual CRUD → defineResource, manual perms → combinators, manual events, etc.)
|
|
138
|
+
- **[mongokit-migration.md](references/mongokit-migration.md)** — Mongoose-only project → mongokit Repository + plugins
|
|
139
|
+
- **[arc-cheatsheet.md](references/arc-cheatsheet.md)** — what arc provides at a glance (defineResource fields, presets, permissions, scope, hooks, events, cache, MCP)
|
|
140
|
+
- **[scaffolding.md](references/scaffolding.md)** — arc CLI (`init`, `generate resource`, `docs`, `introspect`, `doctor`), `.arcrc`, file conventions
|
|
141
|
+
- **[severity.md](references/severity.md)** — severity rubric and triage examples
|