Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ jobs:
version: 1.4.2
- run: npm ci
- run: npm run opa:generate
- name: Verify generated Rego is committed
- name: Verify generated OPA artifacts are committed
run: |
git diff --exit-code -- opa/policies
test -z "$(git ls-files --others --exclude-standard opa/policies)"
git diff --exit-code -- opa
test -z "$(git ls-files --others --exclude-standard opa)"
- run: npm run opa:check
- run: npm run opa:test
- run: npm run opa:server:test
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ npm exec --no -- prd validate --strict
- `init` com agente, instruções, cinco templates de skills e workflow de CI para Copilot.
- Migração aditiva do nosso protótipo anterior e CLI legado isolado.

O runtime `reference` usa a semântica nativa de `evaluateDecision`; `--runtime opa` gera Rego temporário e requer o executável `opa` disponível no PATH (ou em `PRD_OPA_BINARY`). OPA é um runtime candidato de conformance, não a autoridade semântica do modelo.
O runtime `reference` usa a semântica nativa de `evaluateDecision`; `--runtime
opa` recompila somente o subconjunto `dmn-table/v1` em Rego e requer o executável
`opa` no PATH (ou em `PRD_OPA_BINARY`). OPA é um candidato de conformidade, não
executa o produto e não substitui a autoridade semântica do domínio. Use
`--parity` para comparar também os contratos de entrada inválida, gap e overlap.

A [matriz de capacidades](framework/capabilities.json) é o contrato de escopo. **Não há conformidade OMG completa, motor BPMN, FEEL, prova modal, execução de step definitions, servidor MCP ou autenticação de revisores nesta versão.**

Expand Down Expand Up @@ -118,7 +122,7 @@ Cenários Gherkin usam `SCN-...` e não duplicam um arquivo YAML de identidade.
| `test --require-tests --require-bound-scenarios` | Executa casos de decisões e exige vínculos de cenários |
| `decision evaluate ID --input '{...}'` | Avalia uma tabela suportada |
| `decision analyze ID` | Analisa domínios finitos |
| `conformance decision ID --runtime reference|opa` | Compara um runtime executável com a semântica de referência |
| `conformance decision ID --runtime reference\|opa [--parity] [--policy arquivo.rego]` | Compara casos e, opcionalmente, contratos de erro com a referência |
| `cite ID --into notes/design.md` | Preserva snapshot e cita conteúdo |
| `citations check`, `citations refresh arquivo.md` | Verifica ou atualiza citações intactas |
| `change new SLUG --title MOTIVO` | Captura baseline |
Expand Down
19 changes: 19 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
trigger:
- main
variables:
OPA_VERSION: '1.4.2'
pool:
vmImage: ubuntu-latest
steps:
Expand All @@ -14,5 +16,22 @@ steps:
displayName: Unit, integration and legacy tests
- script: npm run intent:validate && npm run intent:test
displayName: Validate product example
- script: |
curl --fail --location --silent --show-error \
--output "$(Agent.TempDirectory)/opa" \
"https://openpolicyagent.org/downloads/v$(OPA_VERSION)/opa_linux_amd64_static"
chmod 0755 "$(Agent.TempDirectory)/opa"
echo "##vso[task.prependpath]$(Agent.TempDirectory)"
displayName: Install OPA $(OPA_VERSION)
- script: |
npm run opa:generate
git diff --exit-code -- opa
git ls-files --others --exclude-standard opa > "$(Agent.TempDirectory)/opa-untracked"
test ! -s "$(Agent.TempDirectory)/opa-untracked"
npm run opa:check
npm run opa:test
npm run opa:server:test
npm run opa:conformance
displayName: OPA generation, server and parity tests
- script: npm pack
displayName: Package CLI
34 changes: 23 additions & 11 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
| `npm test` | regressão automatizada do CLI, compilador e domínio | execução de uma aplicação externa |
| `prd validate --strict` | schemas, referências e invariantes dos perfis | conformidade OMG completa |
| `prd test` | resultados dos casos de decisões tipadas | execução dos passos Gherkin |
| `prd conformance decision ID --runtime opa` | equivalência dos casos entre a semântica de referência e o adapter OPA | equivalência para entradas fora dos casos declarados |
| `prd conformance decision ID --runtime opa` | equivalência dos casos declarados entre a referência e o adapter OPA | semântica fora do subconjunto `dmn-table/v1` |
| `prd conformance decision ID --runtime opa --parity` | paridade para chave extra/ausente, tipo, domínio, gap e overlap | conformidade DMN geral ou entradas arbitrárias |
| `prd citations check` | estado dos blocos e snapshots conhecidos | autenticidade do autor |

Ao adicionar um tipo ou alterar um contrato, atualize em conjunto o schema, o
Expand Down Expand Up @@ -48,17 +49,28 @@ runtime candidato sem mover a autoridade semântica para esse runtime.
```sh
prd conformance decision DEC-001 --runtime reference
prd conformance decision DEC-001 --runtime opa
prd conformance decision DEC-001 --runtime opa --parity
```

O adapter OPA compila somente o subconjunto já suportado: tabela `UNIQUE`,
condições de igualdade, wildcard por condição omitida e saída escalar. Ele não
expande o perfil DMN. O executável `opa` é opcional e não é baixado pelo pacote.
executa o produto nem expande o perfil DMN. Antes de chamar o binário, o adapter
aplica a mesma validação de nomes, tipos e domínios de entrada usada por
`evaluateDecision()`. O Rego também emite esse contrato e estados distintos para
`unique`, `gap`, `overlap` e `invalid_input`.

Uma avaliação normal gera a policy uma vez e envia todos os casos em um único
`opa eval`, limitado por `PRD_OPA_TIMEOUT_MS` (5 segundos por padrão). O resultado
de conformidade registra a versão do binário. `--policy arquivo.rego` exige que o
artefato informado seja idêntico ao gerador para a especificação principal;
policies sintéticas das sondas de paridade continuam temporárias.

### Suíte OPA local e no GitHub Actions

A política versionada em `opa/policies/dec_001.rego` é gerada a partir de
`examples/transfer/product/decisions/DEC-001.yaml`. Para reproduzir a suíte do
CI localmente, instale o executável `opa` no `PATH` e execute:
A policy e o teste versionados em `opa/policies/` e `opa/tests/` são gerados a
partir de `examples/transfer/product/decisions/DEC-001.yaml`. O sufixo hash do
package impede que ids como `DEC-001` e `DEC_001` colidam. Para reproduzir a
suíte do CI localmente, instale OPA 1.4.2 no `PATH` e execute:

```sh
npm ci
Expand All @@ -70,9 +82,9 @@ npm run opa:conformance
```

`opa:check` valida sintaxe estrita e formatação. `opa:test` executa os testes
unitários Rego. `opa:server:test` inicia
temporariamente `opa run --server`, aguarda o health check e valida os quatro
casos pelo endpoint REST `/v1/data/prd/decision/dec_001/result`.
`opa:conformance` exercita o adapter do CLI contra a mesma decisão. O job
`OPA / Rego` também regenera a política e falha quando o arquivo versionado está
desatualizado em relação ao YAML.
Rego gerados, inclusive entradas inválidas. `opa:server:test` reserva uma porta
local livre, inicia temporariamente `opa run --server` e deriva casos, endpoint e
resultados esperados do mesmo YAML. `opa:conformance` usa a policy commitada para
os casos principais, ativa `--parity` e ainda cobre números `0`, `-1`, decimal,
inteiro acima de `2^53` e strings com aspas. GitHub Actions e Azure Pipelines
instalam OPA 1.4.2, regeneram `opa/` e falham se qualquer artefato divergir.
11 changes: 8 additions & 3 deletions docs/VERIFICATION.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Verificação da entrega

Executada em 2026-09-27, Linux, Node.js 24.19.0, npm 11.9.0.
Executada em 2026-10-04, Linux, Node.js 24.19.0, npm 11.9.0 e OPA 1.4.2.

- TypeScript strict e fronteiras domain/application: passaram.
- `npm test`: 50 testes, 50 passaram, zero falhas (23 legado, 27 nativos).
- `npm test`: 64 testes, 64 passaram, zero falhas.
- OPA/Rego: formatação e check estrito passaram; 2 testes Rego passaram; 4
casos passaram pelo servidor HTTP; paridade OPA passou em 11/11 sondas do
exemplo, 4/4 casos numéricos e 1/1 caso com aspas.
- Exemplo integrado: 11 artefatos, 3 cenários e 4 casos de decisão; validação estrita e casos passaram.
- Demo: init, validação, citação, proposta, aprovação, aplicação, stale e refresh/current passaram.
- Pacote npm gerado e instalado em diretório independente. Binário instalado criou projeto em inglês com kit Copilot, validou estritamente e executou os 4 casos com todos os cenários vinculados.
- Metadados das cinco skills e links relativos no scaffold: verificados pela suíte.
- Links locais da documentação: resolvidos.
- Duas citações nativas no exemplo: current.

Não executados nesta sessão: sessão real do Copilot, CI remoto, Windows/macOS ou Node 22. A matriz de CI declara Node 22/24, sem afirmar resultado de jobs remotos. Não há publicação no npm nem configuração de repositório remoto.
Não executados nesta sessão: sessão real do Copilot, CI remoto desta branch,
Azure Pipelines, Windows/macOS ou Node 22. A matriz de CI declara Node 22/24,
sem afirmar resultado de jobs remotos. Não há publicação no npm.

A suíte não é prova de conformidade OMG ou correção de processos em execução. Consulte framework/capabilities.json e os perfis para o escopo exato.
3 changes: 2 additions & 1 deletion framework/capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"finite domain exhaustiveness up to 4096 combinations",
"scenario-linked cases",
"reference runtime conformance contract",
"optional OPA/Rego adapter for declared decision cases"
"optional OPA/Rego adapter for declared decision cases",
"OPA input, gap and overlap error parity probes"
],
"unsupported": [
"FEEL",
Expand Down
20 changes: 0 additions & 20 deletions opa/policies/dec_001.rego

This file was deleted.

81 changes: 81 additions & 0 deletions opa/policies/dec_001_e3d5dad9367a.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package prd.decision.dec_001_e3d5dad9367a

decision_id := "DEC-001"

input_names := ["contaAtiva", "saldoSuficiente"]

valid_input if {
is_object(input)
count(input) == count(input_names)
every name in input_names {
object.get(input, name, {"missing": true}) != {"missing": true}
}
is_boolean(input.contaAtiva)
is_boolean(input.saldoSuficiente)
}

output_valid(value) if {
is_string(value)
value in ["ELEGIVEL", "CONTA_INATIVA", "SALDO_INSUFICIENTE"]
}

# Keeps the matches set defined for an empty or fully filtered rule table.
matches contains {"value": false, "ruleId": ""} if {
false
}

matches contains {"value": "CONTA_INATIVA", "ruleId": "DROW-001"} if {
valid_input
input.contaAtiva == false
}

matches contains {"value": "SALDO_INSUFICIENTE", "ruleId": "DROW-002"} if {
valid_input
input.contaAtiva == true
input.saldoSuficiente == false
}

matches contains {"value": "ELEGIVEL", "ruleId": "DROW-003"} if {
valid_input
input.contaAtiva == true
input.saldoSuficiente == true
}

evaluation := {"status": "invalid_input"} if {
not valid_input
}

evaluation := {"status": "gap", "ruleIds": []} if {
valid_input
count(matches) == 0
}

evaluation := {"status": "overlap", "ruleIds": rule_ids} if {
valid_input
count(matches) > 1
rule_ids := sort([rule.ruleId | some rule in matches])
}

evaluation := {"status": "invalid_output", "ruleId": item.ruleId} if {
valid_input
count(matches) == 1
item := matches[_]
not output_valid(item.value)
}

evaluation := {"status": "unique", "value": item.value, "ruleId": item.ruleId} if {
valid_input
count(matches) == 1
item := matches[_]
output_valid(item.value)
}

result := item if {
evaluation.status == "unique"
item := {"value": evaluation.value, "ruleId": evaluation.ruleId}
}

batch := [outcome |
some item in input
outcome := evaluation with input as item
]
32 changes: 32 additions & 0 deletions opa/tests/dec_001_e3d5dad9367a_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package prd.decision.dec_001_e3d5dad9367a_test

import data.prd.decision.dec_001_e3d5dad9367a.evaluation

decision_cases := [
{"id": "CASE-001", "input": {"contaAtiva": true, "saldoSuficiente": true}, "expected": {"value": "ELEGIVEL", "ruleId": "DROW-003"}},
{"id": "CASE-002", "input": {"contaAtiva": false, "saldoSuficiente": true}, "expected": {"value": "CONTA_INATIVA", "ruleId": "DROW-001"}},
{"id": "CASE-003", "input": {"contaAtiva": true, "saldoSuficiente": false}, "expected": {"value": "SALDO_INSUFICIENTE", "ruleId": "DROW-002"}},
{"id": "CASE-004", "input": {"contaAtiva": false, "saldoSuficiente": false}, "expected": {"value": "CONTA_INATIVA", "ruleId": "DROW-001"}},
]

invalid_inputs := [
{"contaAtiva": true, "saldoSuficiente": true, "__unexpected": true},
{"saldoSuficiente": true},
{"contaAtiva": "__wrong_type__", "saldoSuficiente": true},
]

test_declared_decision_cases if {
every case in decision_cases {
actual := evaluation with input as case.input
actual.status == "unique"
actual.value == case.expected.value
actual.ruleId == case.expected.ruleId
}
}

test_input_contract if {
every invalid_input in invalid_inputs {
actual := evaluation with input as invalid_input
actual.status == "invalid_input"
}
}
37 changes: 0 additions & 37 deletions opa/tests/dec_001_test.rego

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"opa:check": "opa fmt --list --fail opa/policies opa/tests && opa check --strict opa/policies opa/tests",
"opa:test": "opa test opa/policies opa/tests --verbose",
"opa:server:test": "node scripts/test-opa-server.mjs",
"opa:conformance": "node dist/interfaces/cli/main.js -C examples/transfer conformance decision DEC-001 --runtime opa"
"opa:conformance": "node scripts/test-opa-conformance.mjs"
},
"dependencies": {
"commander": "14.0.2", "yaml": "2.9.1", "ajv": "8.17.1",
Expand Down
20 changes: 14 additions & 6 deletions scripts/generate-opa-policies.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { parse } from 'yaml';
import { generateDecisionRego, opaPackageName } from '../dist/infrastructure/opa/rego-generator.js';
import {
generateDecisionRego,
generateDecisionTestRego,
opaPackageName,
} from '../dist/infrastructure/opa/rego-generator.js';

const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const sourcePath = resolve(
repositoryRoot,
process.argv[2] ?? 'examples/transfer/product/decisions/DEC-001.yaml',
);
const targetDirectory = resolve(repositoryRoot, process.argv[3] ?? 'opa/policies');
const policyDirectory = resolve(repositoryRoot, process.argv[3] ?? 'opa/policies');
const testDirectory = resolve(repositoryRoot, process.argv[4] ?? 'opa/tests');
const artifact = parse(readFileSync(sourcePath, 'utf8'));

if (artifact?.kind !== 'Decision' || typeof artifact.metadata?.id !== 'string' || !artifact.spec) {
Expand All @@ -18,8 +23,11 @@ if (artifact?.kind !== 'Decision' || typeof artifact.metadata?.id !== 'string' |

const packageName = opaPackageName(artifact.metadata.id);
const fileName = `${packageName.split('.').at(-1)}.rego`;
const targetPath = resolve(targetDirectory, fileName);
const policyPath = resolve(policyDirectory, fileName);
const testPath = resolve(testDirectory, fileName.replace(/\.rego$/, '_test.rego'));

mkdirSync(targetDirectory, { recursive: true });
writeFileSync(targetPath, generateDecisionRego(artifact.metadata.id, artifact.spec), 'utf8');
process.stdout.write(`Generated ${targetPath}\n`);
mkdirSync(policyDirectory, { recursive: true });
mkdirSync(testDirectory, { recursive: true });
writeFileSync(policyPath, generateDecisionRego(artifact.metadata.id, artifact.spec), 'utf8');
writeFileSync(testPath, generateDecisionTestRego(artifact.metadata.id, artifact.spec), 'utf8');
process.stdout.write(`Generated ${policyPath}\nGenerated ${testPath}\n`);
Loading
Loading