@niscorp/charter
v0.1.2
Published
A universe-blind policy engine — roles resolve glob selections over opaque string universes; the verifier refuses incoherence. Zero dependencies.
Downloads
382
Readme
@niscorp/charter
A universe-blind policy engine. Roles map glob selections over opaque string universes; the engine resolves them to a concrete id set per principal, and a verifier refuses an incoherent charter before it ships. Zero dependencies, by design.
The engine never learns what a string means. You hand it a universe of ids (Nova action ids, table.verb capabilities, anything), a charter that selects over them with globs, and it returns the resolved set. The same code resolves any domain — access control is one algebra, not one per resource kind.
Install
pnpm add @niscorp/charterNo peer dependencies. It imports nothing and manufactures no universe of its own — each governed target exports its own dialect and the composer hands the universe in.
Quick Example
import { resolvePrincipal, verifyCharter } from '@niscorp/charter';
// The charter — role names → selections. `extends` composes whole roles;
// globs select within a universe; deny wins.
const charter = {
viewer: ['crm.contacts', 'crm.*.view'],
sales: { extends: ['viewer'], allow: ['crm.*'], deny: ['crm.*.delete'] },
admin: { extends: ['sales'], allow: ['crm.*.delete', 'settings'] },
};
// The universe of ids that actually exist — the app derives this
// (here, the action ids it ships). The engine never invents it.
const actions = ['crm.contacts', 'crm.contact.view', 'crm.deal.view', 'crm.deal.delete', 'settings'];
// What a principal wearing ['sales'] may reach:
resolvePrincipal(charter, actions, ['sales']);
// Set { 'crm.contacts', 'crm.contact.view', 'crm.deal.view' }
// crm.* matched everything; deny crm.*.delete removed the delete.
// Refuse incoherence at boot — a dead deny, an ambiguous selection,
// an unreachable action all surface here.
const report = verifyCharter(charter, { actions, data: [] });
if (report.errors.length > 0) throw new Error('charter is incoherent');What it is
- Universe-blind.
resolvePrincipal(charter, universe, roles, section)runs the same resolution against any universe.actionsselects Nova action ids;dataselectstable.verbcapabilities (the vex-policy dialect);layoutsselects layout-variant ids (ring 2 — moss's dialect: which variant of an action's layout a principal is served). Adding a governed domain is adding a universe, never new grammar. - A small algebra.
resolved(role) = (∪ extends ∪ match(allow)) − match(deny) − ∪ without. Order-independent, deny wins within a role, denies don't inherit. Roles compose whole roles; each section resolves independently. - Reach is named, not composed. A role may carry
scoping— a name the governed target resolves (vex: which scope behaviors apply). Unlike every section it is not inherited throughextends:resolveScoping(charter, role)is that role's own answer or none, and a principal with several roles is resolved one role at a time. - One glob rule.
*matches any run of characters including dots —crm.*matchescrm.deal.form. No**, no braces, no regex. When a pattern can't be expressed, the fix is a better id. - A verifier that refuses.
verifyCharterruns the opinions the grammar deliberately omits: a dead deny is an error (a typo'd deny fails silent, and silent means unprotected); a dead allow is a warning; ambiguous selections, namespace-as-action, orphan actions, re-allowed ancestor denies all surface. "If it boots, it's coherent."
What it is not
It does not enforce, store documents, or know about HTTP, databases, or your app. The charter and the assignment rows are the app's artifacts. Enforcement is the governed target's job — vex compiles the resolved data set into a scope policy; a shell mounts only the resolved actions. Charter resolves and refuses; it never runs at request time.
