@softize/opus 15.2.1 → 15.2.2
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/CHANGELOG.md +10 -0
- package/bin/lib/gen-runner.mjs +10 -1
- package/docs/adr/0012-data-products-are-first-class-declarations.md +4 -2
- package/docs/data-products.md +7 -1
- package/package.json +14 -15
- package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +0 -0
- package/src/core/data-product.ts +51 -7
- package/src/core/index.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 15.2.2 — 2026-09-11
|
|
11
|
+
|
|
12
|
+
O acesso descritivo de Produtos de Dados usa `permissionContexts` no lugar do nome genérico
|
|
13
|
+
`contexts`. A qualificação evita confundir contextos do framework, contexto de tela, domínio de
|
|
14
|
+
dados e vocabulário de autorização nas projeções do manifest e da Lens.
|
|
15
|
+
|
|
16
|
+
**Migração recomendada:** em declarações `defineDataProduct`, renomeie `access.contexts` para
|
|
17
|
+
`access.permissionContexts` e regenere o manifest. O nome anterior continua aceito e projetado em
|
|
18
|
+
toda a série 15.x como alias deprecated; consumidores novos devem ler somente o nome qualificado.
|
|
19
|
+
|
|
10
20
|
## 15.2.1 — 2026-09-11
|
|
11
21
|
|
|
12
22
|
`opus copy` agora poda diretórios cobertos por um glob recursivo de `copy.exclude`, em vez de
|
package/bin/lib/gen-runner.mjs
CHANGED
|
@@ -220,7 +220,16 @@ function serializeDataProducts(source) {
|
|
|
220
220
|
: [],
|
|
221
221
|
entities: Array.isArray(product.entities) ? product.entities : [],
|
|
222
222
|
access: {
|
|
223
|
-
|
|
223
|
+
permissionContexts: Array.isArray(product.access?.permissionContexts)
|
|
224
|
+
? product.access.permissionContexts
|
|
225
|
+
: Array.isArray(product.access?.contexts)
|
|
226
|
+
? product.access.contexts
|
|
227
|
+
: [],
|
|
228
|
+
contexts: Array.isArray(product.access?.permissionContexts)
|
|
229
|
+
? product.access.permissionContexts
|
|
230
|
+
: Array.isArray(product.access?.contexts)
|
|
231
|
+
? product.access.contexts
|
|
232
|
+
: [],
|
|
224
233
|
organizationalScopes: Array.isArray(product.access?.organizationalScopes)
|
|
225
234
|
? product.access.organizationalScopes
|
|
226
235
|
: [],
|
|
@@ -27,10 +27,12 @@ descritivo e Actions de interface. Produtos ativos podem ser descontinuados com
|
|
|
27
27
|
duplicados e relações literais que apontam para Action ou Entity inexistente. A declaração não
|
|
28
28
|
executa consulta, não contém driver e não substitui uma Action.
|
|
29
29
|
|
|
30
|
-
A autorização continua sendo responsabilidade da Action. `access.
|
|
30
|
+
A autorização continua sendo responsabilidade da Action. `access.permissionContexts` e
|
|
31
31
|
`access.organizationalScopes` documentam o alcance esperado para catálogo, Lens e revisão, mas o
|
|
32
32
|
runtime não os converte em autorização implícita. Essa separação impede que uma descrição
|
|
33
|
-
incompleta abra dados.
|
|
33
|
+
incompleta abra dados. O alias histórico `access.contexts` permanece aceito como entrada e
|
|
34
|
+
projetado no manifest durante a série 15.x por compatibilidade. Novos consumidores usam o nome
|
|
35
|
+
qualificado; o alias será removido somente numa versão major.
|
|
34
36
|
|
|
35
37
|
O manifest projeta a declaração integral. Tools de IA recebem a lista de produtos que expõem em
|
|
36
38
|
metadata, e o servidor MCP publica essa lista em `_meta['com.softize.opus/data-products']`. A
|
package/docs/data-products.md
CHANGED
|
@@ -24,7 +24,7 @@ export const salesLeads = defineDataProduct({
|
|
|
24
24
|
sources: [{ id: 'followize', label: 'Followize' }],
|
|
25
25
|
entities: ['Lead'],
|
|
26
26
|
access: {
|
|
27
|
-
|
|
27
|
+
permissionContexts: ['sales'],
|
|
28
28
|
organizationalScopes: ['unit', 'team'],
|
|
29
29
|
},
|
|
30
30
|
interfaces: ['sale.list', 'sales.performance'],
|
|
@@ -49,6 +49,12 @@ que uma interface deveria respeitar, por exemplo, unidade e equipe. Não é RLS
|
|
|
49
49
|
automática. Toda interface precisa aplicar seus próprios `requires`, `authorize` e recortes no
|
|
50
50
|
handler/repositório, inclusive quando for chamada por IA ou MCP.
|
|
51
51
|
|
|
52
|
+
`permissionContexts` nomeia especificamente chaves do vocabulário de permissão. Ele não descreve
|
|
53
|
+
domínio de dados, Área, departamento nem contexto de tela; projetos que não adotam essa dimensão
|
|
54
|
+
declaram uma lista vazia. O alias `contexts`, publicado originalmente na série 15.x, permanece
|
|
55
|
+
aceito na entrada e projetado no manifest apenas para compatibilidade. Novas declarações e
|
|
56
|
+
consumidores usam o nome qualificado; o alias só poderá ser removido numa versão major.
|
|
57
|
+
|
|
52
58
|
## Projeções
|
|
53
59
|
|
|
54
60
|
`opus gen` publica os produtos no `.opus/manifest.json`. Actions expostas como tools carregam os
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softize/opus",
|
|
3
|
-
"version": "15.2.
|
|
3
|
+
"version": "15.2.2",
|
|
4
4
|
"description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -212,17 +212,6 @@
|
|
|
212
212
|
"bin": {
|
|
213
213
|
"opus": "bin/cli.mjs"
|
|
214
214
|
},
|
|
215
|
-
"scripts": {
|
|
216
|
-
"postinstall": "node ./bin/lib/postinstall.mjs",
|
|
217
|
-
"copy:check": "node ./bin/cli.mjs copy --check",
|
|
218
|
-
"typecheck": "tsc --noEmit",
|
|
219
|
-
"test": "vitest run",
|
|
220
|
-
"test:watch": "vitest",
|
|
221
|
-
"test:cov": "vitest run --coverage",
|
|
222
|
-
"registry:up": "npx -y verdaccio --config ~/.config/verdaccio/config.yaml",
|
|
223
|
-
"release": "bash ./scripts/release.sh",
|
|
224
|
-
"release:local": "bash ./scripts/release.sh --local"
|
|
225
|
-
},
|
|
226
215
|
"dependencies": {
|
|
227
216
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
228
217
|
"@radix-ui/react-checkbox": "^1.1.3",
|
|
@@ -368,11 +357,21 @@
|
|
|
368
357
|
"vitest": "^2.1.0",
|
|
369
358
|
"zod": "^3.24.0"
|
|
370
359
|
},
|
|
371
|
-
"packageManager": "pnpm@9.0.0",
|
|
372
360
|
"repository": {
|
|
373
361
|
"type": "git",
|
|
374
362
|
"url": "git+https://github.com/softize-dev/opus.git",
|
|
375
363
|
"directory": "packages/opus"
|
|
376
364
|
},
|
|
377
|
-
"homepage": "https://opus.softize.com.br"
|
|
378
|
-
|
|
365
|
+
"homepage": "https://opus.softize.com.br",
|
|
366
|
+
"scripts": {
|
|
367
|
+
"postinstall": "node ./bin/lib/postinstall.mjs",
|
|
368
|
+
"copy:check": "node ./bin/cli.mjs copy --check",
|
|
369
|
+
"typecheck": "tsc --noEmit",
|
|
370
|
+
"test": "vitest run",
|
|
371
|
+
"test:watch": "vitest",
|
|
372
|
+
"test:cov": "vitest run --coverage",
|
|
373
|
+
"registry:up": "npx -y verdaccio --config ~/.config/verdaccio/config.yaml",
|
|
374
|
+
"release": "bash ./scripts/release.sh",
|
|
375
|
+
"release:local": "bash ./scripts/release.sh --local"
|
|
376
|
+
}
|
|
377
|
+
}
|
|
File without changes
|
package/src/core/data-product.ts
CHANGED
|
@@ -14,12 +14,21 @@ export interface DataProductSource {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface DataProductAccess {
|
|
17
|
-
/**
|
|
17
|
+
/** @deprecated Use `permissionContexts`; este alias será removido numa versão major. */
|
|
18
18
|
contexts: readonly string[]
|
|
19
|
+
/** Nome qualificado dos contextos de permissão. Sempre presente após `defineDataProduct`. */
|
|
20
|
+
permissionContexts?: readonly string[]
|
|
19
21
|
/** Eixos organizacionais que as Actions precisam considerar, como unit e team. Descritivo. */
|
|
20
22
|
organizationalScopes: readonly string[]
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
export interface DataProductAccessInput {
|
|
26
|
+
permissionContexts?: readonly string[]
|
|
27
|
+
/** @deprecated Use `permissionContexts`; este alias será removido numa versão major. */
|
|
28
|
+
contexts?: readonly string[]
|
|
29
|
+
organizationalScopes: readonly string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
export interface DataProductConfig {
|
|
24
33
|
/** Identidade estável e namespaced, como `sales.leads`. */
|
|
25
34
|
id: string
|
|
@@ -46,6 +55,19 @@ export interface DataProductConfig {
|
|
|
46
55
|
replacedBy?: string
|
|
47
56
|
}
|
|
48
57
|
|
|
58
|
+
export type DataProductInput = Omit<DataProductConfig, 'access'> & { access: DataProductAccessInput }
|
|
59
|
+
type PermissionContextsOf<T extends DataProductAccessInput> = Extract<
|
|
60
|
+
| ('permissionContexts' extends keyof T ? T['permissionContexts'] : never)
|
|
61
|
+
| ('contexts' extends keyof T ? T['contexts'] : never),
|
|
62
|
+
readonly string[]
|
|
63
|
+
>
|
|
64
|
+
export type DefinedDataProduct<T extends DataProductInput = DataProductInput> = Omit<T, 'access'> & {
|
|
65
|
+
access: Omit<T['access'], 'contexts' | 'permissionContexts'> & DataProductAccess & {
|
|
66
|
+
contexts: PermissionContextsOf<T['access']>
|
|
67
|
+
permissionContexts: PermissionContextsOf<T['access']>
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
const ID_RE = /^[a-z][a-z0-9_-]*(?:\.[a-z][a-z0-9_-]*)+$/
|
|
50
72
|
|
|
51
73
|
function nonEmpty(value: unknown, field: string): void {
|
|
@@ -55,7 +77,7 @@ function nonEmpty(value: unknown, field: string): void {
|
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
/** Declara e valida um Produto de Dados sem introduzir dependência de banco ou runtime. */
|
|
58
|
-
export function defineDataProduct<const T extends
|
|
80
|
+
export function defineDataProduct<const T extends DataProductInput>(config: T): DefinedDataProduct<T> {
|
|
59
81
|
if (!ID_RE.test(config.id)) {
|
|
60
82
|
throw new TypeError(`DataProduct id "${config.id}" deve ser namespaced e casar com ${ID_RE.source}`)
|
|
61
83
|
}
|
|
@@ -90,10 +112,23 @@ export function defineDataProduct<const T extends DataProductConfig>(config: T):
|
|
|
90
112
|
throw new TypeError(`DataProduct "${config.id}" possui interface duplicada`)
|
|
91
113
|
}
|
|
92
114
|
for (const action of config.interfaces) nonEmpty(action, 'interfaces[]')
|
|
93
|
-
|
|
94
|
-
|
|
115
|
+
const hasPermissionContexts = Array.isArray(config.access?.permissionContexts)
|
|
116
|
+
const hasLegacyContexts = Array.isArray(config.access?.contexts)
|
|
117
|
+
if ((!hasPermissionContexts && !hasLegacyContexts) || !Array.isArray(config.access?.organizationalScopes)) {
|
|
118
|
+
throw new TypeError('DataProduct "access" deve declarar permissionContexts e organizationalScopes')
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
hasPermissionContexts &&
|
|
122
|
+
hasLegacyContexts &&
|
|
123
|
+
(config.access.permissionContexts!.length !== config.access.contexts!.length ||
|
|
124
|
+
config.access.permissionContexts!.some((context, index) => context !== config.access.contexts![index]))
|
|
125
|
+
) {
|
|
126
|
+
throw new TypeError('DataProduct "access" recebeu permissionContexts e contexts divergentes')
|
|
95
127
|
}
|
|
96
|
-
|
|
128
|
+
const permissionContexts = (
|
|
129
|
+
hasPermissionContexts ? config.access.permissionContexts : config.access.contexts
|
|
130
|
+
) as PermissionContextsOf<T['access']>
|
|
131
|
+
for (const context of permissionContexts) nonEmpty(context, 'access.permissionContexts[]')
|
|
97
132
|
for (const scope of config.access.organizationalScopes) nonEmpty(scope, 'access.organizationalScopes[]')
|
|
98
133
|
if (config.status !== undefined && config.status !== 'active' && config.status !== 'deprecated') {
|
|
99
134
|
throw new TypeError('DataProduct "status" deve ser "active" ou "deprecated"')
|
|
@@ -107,13 +142,22 @@ export function defineDataProduct<const T extends DataProductConfig>(config: T):
|
|
|
107
142
|
} else if (config.replacedBy !== undefined) {
|
|
108
143
|
throw new TypeError('DataProduct ativo não pode declarar "replacedBy"')
|
|
109
144
|
}
|
|
110
|
-
return
|
|
145
|
+
return {
|
|
146
|
+
...config,
|
|
147
|
+
access: {
|
|
148
|
+
...config.access,
|
|
149
|
+
permissionContexts,
|
|
150
|
+
contexts: permissionContexts,
|
|
151
|
+
},
|
|
152
|
+
} as unknown as DefinedDataProduct<T>
|
|
111
153
|
}
|
|
112
154
|
|
|
113
155
|
export function isDataProduct(value: unknown): value is DataProductConfig {
|
|
114
156
|
if (typeof value !== 'object' || value === null) return false
|
|
157
|
+
const access = (value as { access?: { contexts?: unknown } }).access
|
|
158
|
+
if (!Array.isArray(access?.contexts)) return false
|
|
115
159
|
try {
|
|
116
|
-
defineDataProduct(value as
|
|
160
|
+
defineDataProduct(value as DataProductInput)
|
|
117
161
|
return true
|
|
118
162
|
} catch {
|
|
119
163
|
return false
|
package/src/core/index.ts
CHANGED
|
@@ -204,7 +204,10 @@ export type { DomainConfig, FlattenedDomain } from './domain.ts'
|
|
|
204
204
|
export { defineDataProduct, isDataProduct } from './data-product.ts'
|
|
205
205
|
export type {
|
|
206
206
|
DataProductAccess,
|
|
207
|
+
DataProductAccessInput,
|
|
207
208
|
DataProductConfig,
|
|
209
|
+
DataProductInput,
|
|
210
|
+
DefinedDataProduct,
|
|
208
211
|
DataProductSource,
|
|
209
212
|
DataProductStatus,
|
|
210
213
|
} from './data-product.ts'
|