hazo_central
v1.2.0
Published
Tiered-settings governance: resolve along a scope chain, measure cross-scope agreement, govern promotion of a local value to the central default.
Readme
hazo_central
Tiered-settings governance engine for multi-tenant applications. Resolves a setting's effective value along an ordered scope chain, measures cross-scope agreement, and governs promotion of a locally-proven value to the central (global) default through an evidence-backed review queue.
The four verbs
| Verb | Function | Where |
|---|---|---|
| resolve | resolveAll(rows, chain, element) | hazo_central |
| converge | agreementReport(rows, element) / candidates(rows, element) | hazo_central |
| propose | store.create(...) / handlers.createProposal(...) | hazo_central/server |
| promote | applyProposal(id, actor, deps) / handlers.applyProposalRoute(...) | hazo_central/server |
Subpath exports
| Subpath | Contents |
|---|---|
| hazo_central | Types, registry (defineElement/getElement), pure fns (resolve, converge) |
| hazo_central/server | ProposalStore, createCentralHandlers, applyProposal, makeAuditEmitter, IllegalTransitionError |
| hazo_central/client | React hooks: useCandidates, useProposal |
| hazo_central/components | ReviewDashboard, InheritedBadge |
| hazo_central/migrations/001_hazo_central_proposals.sql | PostgreSQL DDL |
| hazo_central/migrations/001_hazo_central_proposals_sqlite.sql | SQLite DDL |
Registering a domain Element
An Element descriptor tells the engine how to key, value, compare, and write rows for a domain. Register once at app startup:
import { defineElement } from "hazo_central";
const configElement = defineElement({
domain: "config-shaped", // unique domain name
keyFn: (r) => r.key, // extract the setting key from a row
valueFn: (r) => r.value, // extract the value from a row
equals: (a, b) => a === b, // compare two values (deep-equal for objects)
accessor: {
list: async (chain) => db.query("SELECT * FROM settings WHERE scope_id = ANY($1)", [chain]),
writeTier: async (scopeId, key, value) => db.upsert("settings", { scope_id: scopeId, key, value }),
},
governance: {
localWritePermission: "manage_org_config", // enforced by the host
centralPromotePermission: "manage_global_config", // enforced by hazo_central routes
},
agreementThreshold: 3, // how many scopes must agree before a key becomes a candidate
});Resolve — get the effective value for a scope chain
import { resolveAll } from "hazo_central";
const chain = [null, orgId]; // null = central tier, then most-specific last
const rows = await configElement.accessor.list(chain);
const resolved = resolveAll(rows, chain, configElement);
// [{ key: "theme", value: "dark", sourceScope: "org-1" }, ...]Converge — find promotion candidates
import { candidates } from "hazo_central";
const cands = candidates(rows, configElement);
// Reports where ≥ agreementThreshold scopes agree on the same valueMount the server routes (Next.js example)
// app/api/central/[...route]/route.ts
import { createCentralHandlers, ProposalStore, makeAuditEmitter } from "hazo_central/server";
import { createCrudService } from "hazo_connect/server";
import { getElement } from "hazo_central";
import "./register-elements"; // side-effect: calls defineElement for each domain
const store = new ProposalStore(createCrudService(adapter, "hazo_central_proposals"));
const handlers = createCentralHandlers({ store, getElement, chain, emitAudit: makeAuditEmitter(adapter) });
export async function GET(req, { params }) {
const { route } = await params;
const ctx = { permissions: getUserPermissions(req), userId: getUserId(req) };
if (route[0] === "candidates") {
const r = await handlers.listCandidates({ domain: req.nextUrl.searchParams.get("domain") }, ctx);
return Response.json(r.body, { status: r.status });
}
// ... PATCH for review, POST for create/apply
}Drop ReviewDashboard into an admin page
import { ReviewDashboard } from "hazo_central/components";
export default function AdminPage() {
return (
<ReviewDashboard
domain="config-shaped"
headers={{ authorization: `Bearer ${token}` }} // forwarded to every API call
/>
);
}ReviewDashboard props:
| Prop | Type | Default | Description |
|---|---|---|---|
| domain | string | — | Domain to list candidates for |
| basePath | string | "/api/central" | API mount path |
| headers | Record<string, string> | — | Extra request headers (e.g. auth token) |
| onPromote | (key: string) => void | — | Called after a successful promote |
Each row shows Promoting… / Promoted ✓ inline, and surfaces any API error (including permission errors) as an inline message without throwing.
Direct promotion mode
By default the engine requires a value to cross the agreementThreshold before a proposal can be applied ("agreement mode"). For settings where an administrator wants to push a value directly — without waiting for organic cross-scope convergence — set promotionMode: "direct" on the element:
const orgDefaultElement = defineElement({
domain: "org-defaults",
promotionMode: "direct", // skip threshold; admin-initiated only
keyFn: (r) => r.key,
valueFn: (r) => r.value,
equals: (a, b) => a === b,
accessor: { list, writeTier },
governance: {
localWritePermission: "local_write",
centralPromotePermission: "manage_global_config",
},
});In direct mode applyProposal still throws StaleEvidenceError if the proposed value has been completely superseded — it just doesn't require a quorum.
Targeting a non-central tier
By default a proposal writes to the central tier (scope_id: null). Pass target_scope_id to store.create() (or the createProposal HTTP handler) to promote to a specific scope instead:
// Via store directly
await store.create({
domain: "org-defaults",
setting_key: "invoice_template",
proposed_value: "v2",
evidence: { ... },
proposed_by: actorId,
target_scope_id: "org-42", // writes to org-42, not central
});
// Via HTTP handler body
POST /api/central/proposals
{ "domain": "org-defaults", "setting_key": "invoice_template", "proposed_value": "v2", "target_scope_id": "org-42" }target_scope_id is persisted on the proposal as scope_id; applyProposal reads that field when calling writeTier.
Catching typed proposal errors
applyProposalRoute converts known errors to HTTP status codes. If you call applyProposal directly, catch the typed classes:
import { applyProposal, StaleEvidenceError } from "hazo_central/server";
import { IllegalTransitionError } from "hazo_central/server";
try {
await applyProposal(id, actor, deps);
} catch (e) {
if (e instanceof StaleEvidenceError) { /* evidence went stale — re-tally */ }
if (e instanceof IllegalTransitionError) { /* wrong status transition (e.g. apply a rejected proposal) */ }
}Tailwind v4
If using components, add to your app's CSS entry:
@source "../node_modules/hazo_central/dist";License
MIT
