@atlasent/contract-parity
v0.1.2
Published
Canonical cross-runtime policy parity vectors + reference runner. Every AtlaSent runtime that evaluates policy (V1 wire, control-plane, SDK replay) MUST run this suite in CI. Vendoring out is forbidden — import this package instead so the contract stays s
Maintainers
Readme
@atlasent/contract-parity
Canonical cross-runtime policy parity vectors + reference runner for the AtlaSent platform.
Related gate: V1_GATES.md § G1
Prose spec: POLICY_PARITY_CONTRACT.md
Vector source: contract/parity/vectors/
Every AtlaSent runtime that evaluates policy (V1 wire in atlasent-api,
control-plane in atlasent-control-plane, SDK offline replay in
atlasent-sdk) MUST run this suite in CI. The vectors here are the single
source of truth — vendoring out is forbidden, import this package instead
so the contract stays single-sourced.
Install
npm install --save-dev @atlasent/contract-parityUsage
Adapt your runtime's evaluate function to the EvaluateAdapter shape,
then call runParity:
import { runParity } from "@atlasent/contract-parity";
import { StubPolicyEngine } from "./policyEngine.js";
const engine = new StubPolicyEngine();
const { passed, failed, total, results } = await runParity({
engine: "stub",
evaluate: (input) => engine.evaluate(input),
});
if (failed > 0) {
for (const r of results.filter((r) => !r.ok)) {
console.error(`✗ ${r.name}`, r);
}
process.exit(1);
}Or inside vitest:
import { describe, expect, it } from "vitest";
import { vectorsForEngine } from "@atlasent/contract-parity";
import { StubPolicyEngine } from "./policyEngine.js";
const engine = new StubPolicyEngine();
describe("Policy parity — stub vectors", () => {
for (const v of vectorsForEngine("stub")) {
it(v.name, async () => {
const outcome = await engine.evaluate(v.input);
expect(outcome.decision).toBe(v.expected.decision);
const reasonsText = (outcome.reasons ?? []).join("\n");
for (const needle of v.expected.reasons_includes ?? []) {
expect(reasonsText).toContain(needle);
}
});
}
});Exports
VECTORS— the full array ofParityVectorobjects.vectorsForEngine(engine: "stub" | "opa"): ParityVector[]— vectors whoseenginematches, includingany.runParity({ engine, evaluate, log? })— reference runner, returns{ passed, failed, total, results }. Assertsdecisionmatches and everyreasons_includessubstring appears in the runtime's actualreasons[].ParityVector,EvaluateAdapter,RunParityResulttypes.
How vectors stay in sync with the canonical JSON
Vectors live canonically at contract/parity/vectors/*.json.
scripts/generate-vectors.mjs runs at prebuild and writes
src/vectors.generated.ts from the JSON sources. The generated file is
committed so reviewers can diff vector changes without running the build,
and so the published package contains the data even if a downstream
build forgets the prebuild step.
If you change vectors:
- Edit
contract/parity/vectors/*.json(the canonical source). - Run
pnpm --filter @atlasent/contract-parity generate(or justpnpm --filter @atlasent/contract-parity buildwhich prebuilds). - Commit both the JSON and the regenerated
vectors.generated.ts. - Bump the package's semver appropriately:
- patch — added a vector (contract becomes stricter, additive).
- minor — added a new exported helper or vector with new shape (back-compat).
- major — removed/renamed a vector or changed schema (breaking).
Vector schema
See contract/parity/schema.json for
the JSON Schema (draft-07) the source files validate against. Required
fields: name, engine, input.{action, resource.type, subject},
expected.decision. Optional: expected.reasons_includes,
expected.permit.
Release process
Publishing is automated via
.github/workflows/contract-parity-release.yml
on a tag of the form contract-parity-v<semver>:
# from a clean main with the version bumped in package.json
git tag contract-parity-v0.1.0
git push origin contract-parity-v0.1.0The workflow:
- Regenerates
src/vectors.generated.tsfrom the canonical JSON. - Builds
dist/(cjs + esm + dts) viatsup(pulled in vianpx -yso the workspace lockfile doesn't need a dedicated devDep). - Verifies all three dist artifacts exist.
- Packs the tarball and uploads as a workflow artifact (always).
- Publishes to npm with provenance attestation (only on tag push or
workflow_dispatch with
dry_run=false).
workflow_dispatch defaults to dry_run: true so manual runs only pack,
never publish. Set dry_run: false explicitly to publish without a tag.
Requires the repository secret NPM_TOKEN (publish rights to the
@atlasent scope).
Consumer migration status
v0.1.0 of this package supersedes the inlined vector copies in:
atlasent-control-plane/api/src/lib/policyEngine.parity.test.tsatlasent-api/supabase/functions/v1-evaluate/parity.test.ts
Both migrations are tracked under
V1_GATES.md G1
"Deferred follow-ups" and will land in two follow-up PRs once v0.1.0 is
published to npm.
