Policy and rule engine
Purpose¶
This document specifies the policy engine: the deterministic function service that decides, for every action a Chonk wants to take, whether it may happen, under which autonomy class, and with what constraints. It is the enforcement point for the PRD's guardrails (§11, GD-1), called by the Harness in the Pre-check stage of the decision pipeline (Decision Pipeline); it is never merely a prompt instruction. It defines the rule language, the autonomy-class lattice and the narrowing-only invariant, hard lines, precedence, evaluation, versioning and rollout, testing, the simulation harness, and the default rule set that ships with every tenant.
Scope / Non-scope¶
In scope
- The rule DSL: subject, scope, condition, effect, class, constraints, effective dates, priority.
- The action-type registry and per-action floor classes.
- Autonomy classes, graduation clamps, listening-period behaviour, the narrowing invariant.
- Hard lines (platform invariants and owner-set invariants) and how they compose.
- Precedence, conflict resolution, evaluation order, performance budget.
- Ruleset versioning, semantic diffs, staged rollout, rollback.
- Rule testing (unit, property-based, replay) and the simulation harness.
- The default rule set and worked evaluation traces.
Non-scope
- The scoring functions the engine consumes (blast radius, novelty, confidence) — Decision Pipeline.
- Breaker and kill-switch semantics — Closed-Loop Risk Mitigation; this doc only defines how their state clamps a decision.
- Who receives a routed decision and what happens when they do not answer — Escalation and Human-in-the-Loop.
- Content linting (tone, protected characteristics in text) — the engine consumes lint results as context fields; linters live in Communication Engine Rules.
1. Position in the system¶
The policy engine is a pure function, resolve(request: PolicyRequest): PolicyDecision, with no I/O of its own. The Harness assembles the EvalContext (outreach counters, budget envelopes, labor-rule results, lint flags, scores) before calling it and stores the full request inside the receipt, so any decision can be re-resolved later byte-for-byte. Three consequences:
- Models never hold authority. A Chonk may read the active ruleset to avoid proposing things that will be refused, but its reading has no effect. The Harness calls
resolveregardless of what the model believes. - Every decision is replayable. Receipts carry the pinned
ruleset_versionand the exact context;resolveover a stored request reproduces the stored decision or the test suite fails. - Rules are data, not code. Rulesets are versioned documents that pass lint, simulation, canary and activation. Changing a rule is itself an action in the pipeline (R-PLAT-010).
2. The policy language¶
2.1 Design constraints¶
| Constraint | Meaning | Enforced by |
|---|---|---|
| Declarative | A rule states what is true, not how to check it. | Schema |
| Total | Every registered action type resolves in every context; no matching rule means DENY, not an exception. |
Lint coverage check + runtime fallback |
| Deterministic | Same request, same ruleset, same decision. The only clock is ctx.now. |
Pure evaluator |
| Bounded | Conditions have a 64-node cap, no loops, no recursion. | Parser |
| Monotone | Tenant configuration can only make a decision more restrictive than the shipped default. | Lint narrowing check + runtime assert |
| Explainable | Every decision carries the disposition of every candidate rule. | Trace contract |
2.2 Rule schema¶
type AutonomyClass = "AUTONOMOUS" | "PROPOSE_CONFIRM" | "HUMAN_ONLY";
type Layer = "platform" | "owner" | "tenant" | "default";
type Authority = "platform" | "owner" | "org_lead"; // who may author at this layer
type Function = "planning" | "outreach" | "approvals" | "scheduling" | "feedback"
| "hr" | "comms" | "data" | "escalation" | "cadence" | "agents" | "policy";
type Rule = {
id: string; // "R-APR-002"; stable across versions
version: number; // bumps on any field change
layer: Layer;
hard_line: boolean; // legal only when layer ∈ {platform, owner}
authored_by: { authority: Authority; actor_id: string; author_scope: Scope };
subject: { actor: ChonkRole[] | "*"; action: ActionPattern }; // "spend.approve" | "chase.*" | "*"
scope: Scope;
condition?: Expr; // absent = always
on_missing: "match" | "skip"; // when a ctx path the condition needs is absent
effect: "allow" | "deny";
class?: AutonomyClass; // required when effect = "allow"
constraints: Constraint[];
priority: number; // 0..1000; tiebreak within equal specificity
effective: { from: ISODate; to?: ISODate; windows?: Window[];
tz: "person" | "location" | "tenant" | IANATimezone };
reason: string; // shown in receipts
owner: ChonkRole | "owner" | "platform";
tests: string[]; // unit tests that must pass for activation
};
type Scope = { tenant?: TenantId; mode?: "full" | "org" | "function" | "atoms";
org?: OrgPath; // "ops/support/tier2" — prefix match, depth matters
role?: RoleId; person?: PersonId; location?: LocationId;
channel?: "slack" | "teams" | "sms" | "voice" | "email" | "app"; function?: Function };
2.3 Subject: the action-type registry¶
Every write a Chonk can perform is a registered action type of the form domain.verb[.qualifier]. The registry (owned by the Chief Security Officer, stored as Policy nodes in the Org Model) maps each type to a Function, a floor class (the least restrictive class the type can ever have), write and externality flags, and the compensator used by the pipeline. Unregistered action types resolve to DENY and raise a novelty alarm (EC-POL-006). A representative slice:
| Action type | Function | Floor | External | Compensator |
|---|---|---|---|---|
brief.send, chase.internal, checkin.ask |
outreach | AUTONOMOUS | no | message.retract |
chase.vendor |
outreach | PROPOSE_CONFIRM (first contact) | yes | message.correct |
meeting.book |
outreach | AUTONOMOUS | no | meeting.cancel |
plan.rebalance |
planning | AUTONOMOUS | no | orgmodel.restore_version |
plan.change.headcount, goal.set.quarterly |
planning | PROPOSE_CONFIRM | no | orgmodel.restore_version |
spend.approve |
approvals | AUTONOMOUS | yes | ledger.void or ledger.flag_clawback |
contract.sign |
approvals | HUMAN_ONLY | yes | none (human) |
shift.swap, shift.callout.fill |
scheduling | AUTONOMOUS | no | shift.swap_back / shift.unassign |
shift.dispute.resolve |
scheduling | HUMAN_ONLY | no | none |
feedback.concern |
feedback | AUTONOMOUS | no | message.retract + memory.annotate |
pto.approve / pto.deny |
hr | AUTONOMOUS / PROPOSE_CONFIRM | yes (HRIS) | hris.revert |
hr.terminate, hr.offer.extend, hr.comp.change, hr.promote, hr.discipline |
hr | HUMAN_ONLY | yes | none |
external.publish |
comms | PROPOSE_CONFIRM | yes | post.unpublish |
data.read.* |
data | AUTONOMOUS | no | n/a |
data.export |
data | PROPOSE_CONFIRM | yes | none (irreversible) |
escalation.suppress |
escalation | DENY (no floor) | – | – |
policy.rule.write / policy.hardline.write |
policy | PROPOSE_CONFIRM / HUMAN_ONLY | no | ruleset.rollback / none |
The full registry is in Data Model and API Reference. The floor is the first thing lint checks: no rule at any layer may assign a class below it.
2.4 Scope and specificity¶
A rule applies when every field present in its scope matches the request's scope. org matches by path prefix (ops matches ops/support), so depth is meaningful. Specificity is a lexicographically compared tuple:
specificity(rule) = (scope_score, action_score, priority)
scope_score = (person ? 32 : 0) + (location ? 16 : 0) + (role ? 8 : 0)
+ (org ? 4 + min(depth(org), 3) : 0) + (channel ? 2 : 0)
+ (function ? 1 : 0) + (mode ? 1 : 0)
action_score = exact type ? 3 : prefix pattern ("spend.*") ? 2 : "*" ? 0
Scope beats action pattern: "for Sam, everything in chase.* is PROPOSE_CONFIRM" outranks "tenant-wide, chase.internal is AUTONOMOUS". Priority is the last component, so it only orders rules with identical scope and action shapes.
2.5 Conditions¶
Conditions are boolean expressions over the typed EvalContext. The grammar is deliberately small:
expr := or ; or := and ("or" and)* ; and := not ("and" not)* ; not := "not" not | cmp
cmp := val (("==" | "!=" | "<" | "<=" | ">" | ">=") val)? | val "in" list | val "matches" REGEX
val := LITERAL | PATH | FN "(" args ")" | "(" expr ")"
FN := len | hours_since | days_until | local_hour | pct | has | min | max | starts_with
No lambdas, loops or user functions; at most 64 AST nodes; every PATH must exist in the EvalContext schema (compile error otherwise). When a schema path is absent at runtime (an integration was down and a counter is unknown), on_missing decides: match (fail closed — default for deny and HUMAN_ONLY rules) or skip (default for allow rules). The trace records ctx_missing either way. The context the Harness supplies (abridged; full type in doc 17):
type EvalContext = {
now: ISODate;
tenant: { id; mode; listening_until?: ISODate;
graduation: Record<Function, "SHADOW" | "PROPOSE" | "AUTONOMOUS">;
kill_switch: 0 | 1 | 2 | 3; breakers: Record<Function, "closed" | "open" | "half_open">;
policy: { auto_limit: number; quorum_limit: number } };
actor: { chonk: ChonkRole; subagent?: string; run_id: string };
action: { type: string; function: Function; write: boolean; external: boolean; params: object };
target?: { person?: PersonRef; org?: OrgPath; location?: LocationId; vendor?: VendorId };
person?: { id; role; org_path; local_time: ISODate; on_pto: boolean; is_minor: boolean;
employment_type: "employee" | "contractor" | "agent";
outreach_today: number; last_outreach_at?: ISODate; opted_out: string[] };
money?: { amount: number; currency: string; category: string; vendor_known: boolean;
envelope: { budget: number; spent: number } };
plan?: { affects_headcount: boolean; affects_budget: boolean; cross_org: boolean;
external_commitment: boolean; deadline_delta_days: number;
allocation_delta_pct: number; org_rebalances_this_week: number };
labor?: { violations: string[]; overtime_delta_hours: number; notice_days: number };
content?: { topics: string[]; refs_protected_characteristic: boolean; claims_human: boolean;
compares_named_peers: boolean; has_evidence_links: boolean; has_path_forward: boolean;
has_citation: boolean; injection_flag: boolean };
written_attempts?: { count: number; first_at?: ISODate };
scores: { blast_radius: number; novelty: number; confidence: number; people_affected: number };
history: { same_shape_successes_90d: number; concerns_same_theme_30d: number;
overrides_30d_for_rule: Record<string, number> };
};
2.6 Effects, classes, constraints¶
effect: deny means the Harness will not perform the action under any class and will not route it to a human as a proposal (a human may still do the thing outside Sea Suite; the Harness refuses to be the instrument). effect: allow carries a class and zero or more constraints:
type Constraint =
// parameters — resolved by specificity, clamped by hard lines
| { kind: "max_amount"; value: number; currency: string; on_violation: "elevate" | "deny" }
| { kind: "rate_limit"; per: "person" | "org" | "tenant" | "vendor" | "item" | "owner";
window: Duration; max: number; on_violation: "defer" | "deny" }
| { kind: "expiry"; duration: Duration; on_expiry: "no_action" | "escalate_up" | "delegate_to_mor" }
| { kind: "route"; to: "manager_of_record" | "owner" | "ic" | "project_owner" | "ops" }
// obligations — merged to the tightest across all matching rules
| { kind: "quiet_hours"; from: HHMM; to: HHMM; tz: "person" | "location"; on_violation: "defer" }
| { kind: "requires"; what: ("written_attempt" | "evidence_links" | "path_forward" | "citation"
| "reason_in_invite" | "ic_visibility" | "explanation" | "human_edit_window")[] }
| { kind: "quorum"; approvers: number }
| { kind: "cooling_off"; duration: Duration }
| { kind: "reversal_window"; duration: Duration }
| { kind: "purpose"; allowed: Purpose[] }
| { kind: "max_blast_radius"; value: number; on_violation: "elevate" }
| { kind: "min_confidence"; value: number; on_violation: "elevate" }
| { kind: "max_novelty"; value: number; on_violation: "elevate" };
elevate moves one step up the class lattice (§3.1). defer holds the action and re-evaluates when the window or counter allows; nothing is dropped silently. Merge semantics:
| Kind | "Tighter" means | Merge across matching rules |
|---|---|---|
max_amount, rate_limit, expiry |
lower amount; lower max or longer window; shorter, and no_action safer than escalate_up |
most specific wins, then clamped to any hard-line value |
route |
– | most specific wins |
quiet_hours, requires |
larger interval; more obligations | union |
quorum, cooling_off, reversal_window, min_confidence |
more, longer, higher | max |
purpose |
fewer purposes | intersection |
max_blast_radius, max_novelty |
lower | min |
Parameters are where carve-outs live (an on-call channel may need a higher chase cap). Obligations never need carve-outs and are therefore cumulative.
2.7 Effective dates and windows¶
effective.from/to bound the rule's life; windows express recurrence ({ days: ["mon".."fri"], from: "20:00", to: "08:00" }) in the timezone named by tz. tz: "person" resolves through the person's Org Model record; if unknown, the evaluator falls back to location, then tenant timezone, and widens any quiet-hours window by one hour on each side (fail toward not messaging). A rule outside its window is traced as out_of_window and does not match.
2.8 Priority¶
priority (0–1000, default 100) orders rules that are otherwise indistinguishable in specificity. It never lets a rule beat a more specific one, and never lets an allow beat a deny or a HUMAN_ONLY (§5). Its main use is ordering several tenant-level threshold rules for one action.
2.9 Example rules¶
- id: R-APR-002
version: 3
layer: default
hard_line: false
subject: { actor: ["Chief Fin Officer"], action: "spend.approve" }
scope: {}
condition: "money.amount > tenant.policy.auto_limit and money.amount <= tenant.policy.quorum_limit"
on_missing: match
effect: allow
class: PROPOSE_CONFIRM
constraints:
- { kind: route, to: owner }
- { kind: expiry, duration: P5D, on_expiry: no_action }
- { kind: reversal_window, duration: PT1H }
- { kind: requires, what: [explanation] }
priority: 100
effective: { from: 2026-09-01, tz: tenant }
reason: "Over-threshold spend needs a human tap with receipts (PRD §6, FN-1)."
owner: Chief Fin Officer
tests: [T-APR-002-a, T-APR-002-b]
- id: HL-TIDEPOOL-003 # owner hard line, compiled from the direction editor
version: 1
layer: owner
hard_line: true
subject: { actor: "*", action: "discount.grant" }
scope: { tenant: tidepool }
condition: "params.discount_pct > 20"
on_missing: match
effect: deny
reason: "Never discount below 20% off list. — owner, 2026-09-02"
owner: owner
3. Autonomy classes and the narrowing invariant¶
3.1 The lattice¶
AUTONOMOUS < PROPOSE_CONFIRM < HUMAN_ONLY < DENY
DENY is an effect, not a class, but sits at the top of the lattice for "more restrictive". SHADOW is not a class either: it is an execution mode applied by the graduation clamp — the decision keeps its class and the Act stage writes to the shadow log instead of the world. elevate(c) moves one step up; nothing in the engine ever moves down.
3.2 Floors¶
The registry floor is the least restrictive defensible class for an action. Floors for the HUMAN_ONLY set (hiring and termination decisions, compensation, promotion, discipline, legal, safety decisions, anything touching protected characteristics) are platform invariants: no layer can lower them and lint rejects any rule that tries (EC-POL-004).
3.3 Narrowing-only¶
- Against the floor. Any rule at any layer:
class ≥ floor(action). Lint error otherwise. - Against higher authority. A rule authored by an
org_lead(a manager of record configuring their own org) must be at least as restrictive, on every context where it matches, as the effective decision produced by owner- and platform-authored rules on that scope. Lint evaluates this against the simulation corpus (§9) restricted to the rule's scope and rejects if any sample gets a lower class or a looser parameter. The owner is notified of the rejected attempt. - Owners can carve out down to the floor. An owner may write a person- or org-scoped rule that is less restrictive than their own tenant-wide rule, never below the floor. A rule the owner wants un-carve-out-able, even by themselves in a hurry, is a hard line (§4).
The property the tests check: for any ruleset S, non-hard tenant rule r, and context x, class(resolve(S ∪ {r}, x)) ≥ class(resolve(S_default ∪ S_platform, x)) — adding tenant rules never gets below the shipped defaults.
3.4 Graduation clamp and the listening period¶
Graduation is tracked per (tenant, function); how it is earned and lost is the trust-budget model in Closed-Loop Risk Mitigation. The engine only applies the clamp:
| Graduation state | Effect on an allow decision |
|---|---|
SHADOW |
mode = shadow; class unchanged; nothing leaves the Harness except HUMAN_ONLY packets, which go to the shadow inbox |
PROPOSE |
AUTONOMOUS becomes PROPOSE_CONFIRM; others unchanged |
AUTONOMOUS |
no clamp |
During the listening period (now < tenant.listening_until, two weeks from onboarding approval) every function is SHADOW regardless of stored state, with three whitelisted exceptions expressed as a platform rule (R-PLAT-007): intro.send and question.ask (the "meet everyone" part of listening, capped at 3 messages per person per week) and plan.propose.first (PROPOSE_CONFIRM to the owner). The IC escalation path is not governed by graduation at all (R-PLAT-002, R-ESC-002).
3.5 Per-instance elevation¶
The engine returns a class; the pipeline may elevate it for one instance based on scores (min_confidence, max_blast_radius, max_novelty) and on breaker or kill-switch state. Those clamps are computed inside resolve when the scores are present, so the receipt has one authoritative class. The pipeline re-calls resolve immediately before Act (doc 06 §4.4); the result can only be more restrictive than the Pre-check result because HUMAN_ONLY and DENY are sticky per decision (§5.1).
4. Hard lines¶
4.1 Platform invariants¶
Platform hard lines ship with the system, are identical for every tenant, and cannot be edited through any product surface. They are rules R-PLAT-* with layer: platform, hard_line: true and express only deny, HUMAN_ONLY, or tightening obligations: the HUMAN_ONLY set; never suppress or delay an IC escalation; never read private channels, DMs the manager is not party to, keystrokes or cameras; never share data across tenants; never modify the audit log; never claim to be human.
4.2 Owner hard lines¶
Owner hard lines are the "hard lines" of the Owner Console direction editor (OW-1): "never discount below 20%", "no overtime without approval", "no customer-facing message without a human", "nobody works more than five consecutive days". The console compiles them to rules with layer: owner, hard_line: true. A Chonk may draft the text, but submission requires the owner's own authentication (policy.hardline.write is HUMAN_ONLY). They may express only: effect: deny under a condition; class: HUMAN_ONLY or PROPOSE_CONFIRM for an action type; parameter clamps at least as tight as the default; obligations. Nothing else compiles. That is what makes "override everything" safe: a hard line can only ever remove options.
4.3 Why hard lines override everything¶
Because hard lines can only restrict, they compose as clamps rather than as competing rules. resolve treats a matched hard line as absorbing (a deny or HUMAN_ONLY hard line ends evaluation with that result) or as a clamp on parameters. Two hard lines never conflict: two denies are a deny; a deny and a HUMAN_ONLY is a deny; two max_amount values clamp to the smaller. No tenant rule, priority, specificity or graduation state can produce a result a matched hard line forbids; property test PB-4 proves it for every generated ruleset.
5. Precedence and conflict resolution¶
5.1 Principles, in order¶
| # | Principle | Statement |
|---|---|---|
| P1 | Hard lines first | A matched platform or owner hard line that denies, or requires HUMAN_ONLY, is the decision. Hard-line parameters clamp the final decision. |
| P2 | Deny is absorbing | Any matched deny at any layer and specificity wins. You cannot "allow your way out" of a deny with a more specific rule; you edit the deny's condition. |
| P3 | HUMAN_ONLY is sticky | Any matched HUMAN_ONLY rule wins over any allow at any specificity. Per decision, once an instance is classified HUMAN_ONLY it can never be reclassified lower, even if the context changes (the amount shrinks, the rule is edited). |
| P4 | Most-specific scope wins | Among remaining allow rules, the highest specificity tuple selects the class and the parameter constraints. |
| P5 | Restrictive tiebreak | Identical specificity: most restrictive class; then tightest parameters; then lexical rule id (determinism). |
| P6 | Obligations accumulate | Obligations from every matched rule, hard lines included, merge to the tightest. |
| P7 | Runtime clamps last | Graduation, breakers, kill switch, and score-based elevation can only raise the class. |
| P8 | Floor assert | The final class is asserted ≥ floor. A failed assert means lint was bypassed: SEV1 integrity incident, decision becomes DENY. |
5.2 Algorithm¶
function resolve(req):
rs = rulesets.pinned(req.run_id) # immutable snapshot for the run
cands = rs.index.byAction(req.action.type) # trie: exact, prefix, "*"
trace = []; matched = []
for r in cands:
if not withinEffective(r, req.now, tz(r, req)): trace.add(r, "out_of_window"); continue
if not scopeMatches(r.scope, req.scope): trace.add(r, "scope_miss"); continue
m = evalCondition(r.condition, req.ctx) # true | false | MISSING
if m == MISSING: m = (r.on_missing == "match"); trace.add(r, "ctx_missing")
trace.add(r, m ? "matched" : "cond_false")
if m: matched.add(r)
floor = registry.floor(req.action.type)
if floor == undefined: return decision(DENY, why="unregistered_action", trace) # EC-POL-006
hard = matched.filter(hard_line)
for pool in [hard, matched]: # P1, P2 (hard first so the trace names them)
d = pool.filter(effect == "deny"); if d.nonEmpty: return decision(DENY, winning=d, trace)
for pool in [hard, matched]: # P1, P3
h = pool.filter(class == HUMAN_ONLY); if h.nonEmpty:
return decision(HUMAN_ONLY, obligations=mergeObligations(matched), winning=h, trace)
allows = matched.filter(effect == "allow")
if allows.isEmpty: return decision(DENY, why="no_rule", trace) # totality
top = allows.sortBy(specificity desc, restrictiveness desc, id asc)[0] # P4, P5
cls = top.class
params = clampParams(resolveParams(allows), hard) # per kind by specificity, then P1 clamps
oblig = mergeObligations(matched) # P6
deferUntil = none
for o in oblig: # P7: instance clamps
if violated(o, req.ctx):
if o.on_violation == "elevate": cls = elevate(cls)
if o.on_violation == "deny": return decision(DENY, winning=[o.source], trace)
if o.on_violation == "defer": deferUntil = max(deferUntil, nextAllowed(o, req.ctx))
cls = max(cls, graduationClamp(req.ctx.tenant.graduation[req.action.function]))
cls = max(cls, breakerClamp(req.ctx.tenant.breakers[req.action.function]))
cls = max(cls, killSwitchClamp(req.ctx.tenant.kill_switch, req.action))
mode = (graduation == SHADOW or kill_switch >= 2) ? "shadow" : "live"
if cls < floor: raiseIntegrityIncident("floor_violation", req); return decision(DENY, trace) # P8
return decision(ALLOW, cls, params ∪ oblig, mode, deferUntil, winning=top, trace)
5.3 Decision shape¶
type PolicyDecision = {
effect: "allow" | "deny";
class?: AutonomyClass;
mode: "live" | "shadow";
defer_until?: ISODate;
constraints: Constraint[];
winning: RuleRef[]; // ids + versions that determined the outcome
trace: { rule: RuleRef; disposition: "matched" | "cond_false" | "scope_miss" | "out_of_window" | "ctx_missing" }[];
clamps: { graduation?: string; breaker?: string; kill_switch?: number; elevations: string[] };
ruleset_version: string; evaluated_at: ISODate; latency_us: number;
};
6. Evaluation order and performance¶
- Compilation. On activation a ruleset compiles to an action trie (exact → prefix → wildcard), per-rule scope matchers, and condition ASTs with paths resolved to context offsets. Compile errors (unknown path, node cap, floor violation, narrowing violation, non-compiling hard line) block activation.
- Context precomputation. The Harness computes counters and lookups once per run and passes them in. The engine performs no I/O and never queries the Org Model during evaluation.
- Pinning. A run pins
ruleset_versionat Observe; all decisions in the run use it. Rollouts affect the next run, never one in flight. - Budget. p50 < 1 ms, p99 < 5 ms per
resolvein-process, for rulesets up to 2,000 rules and contexts up to 64 KB. Above 50 ms is a metric alarm; above 500 ms fails closed (DENY, reasoneval_timeout) and pages ops. - Caching. Decisions are memoised on
(ruleset_version, action, scope_hash, ctx_hash)for the life of the run only;now-dependent windows make a cross-run cache wrong. - Ordering. Trace order is candidate order (trie order, then rule id), stable across evaluations, which keeps diffs readable.
7. Versioning, diffs, staged rollout, rollback¶
7.1 Lifecycle¶
stateDiagram-v2
[*] --> Draft
Draft --> Linted: lint passes (floor, narrowing, coverage, compile, tests)
Draft --> Draft: lint fails
Linted --> Simulated: simulate against last 90d receipts + sim org
Simulated --> Draft: report rejected
Simulated --> Canary: owner tap (policy.ruleset.activate = PROPOSE_CONFIRM)
Canary --> Active: canary window clean
Canary --> RolledBack: diff alarm or manual
Active --> Superseded: newer ruleset activated
Active --> RolledBack: rollback
RolledBack --> [*]
Superseded --> [*]
A ruleset is an immutable, content-addressed document; version is <tenant>@<monotonic int> and the receipt stores the content hash too. Individual rules keep stable ids and bump version on change, so "what changed for R-APR-002" is answerable across rulesets.
7.2 Semantic diff¶
A textual diff of YAML is necessary but insufficient. The engine produces a decision diff: for each action type, over a fixed panel of contexts (the simulation corpus plus the last 90 days of real requests from receipts), the set of (context → old decision, new decision) pairs that changed, grouped:
| Bucket | Meaning | Gate |
|---|---|---|
widened |
new class lower, or a parameter looser | Only owners may activate; never allowed for org_lead authors |
narrowed |
more restrictive | Activates freely (still simulated) |
rerouted |
same class, different route or expiry | Affected humans' inbox capacity checked against doc 09 §7 caps |
coverage |
action types that gained or lost any matching rule | Loss of coverage = DENY at runtime; lint blocks it |
7.3 Staged rollout¶
Two mechanisms, usable together:
- Canary by scope. Activate for one org (or one location in Atoms) for a window (default 7 days or 200 decisions, whichever is later). The canary is the new ruleset with an extra top-level scope filter; no special code path.
- Dual evaluation. Both rulesets evaluate every request; the old one is enforced; every disagreement is logged as
policy.shadow_diffwith both traces. Promotion requires thewidenedcount to be zero unless the owner explicitly accepts each widened case in the promotion tap.
Automatic abort: a canary is rolled back without asking if any widened decision is followed by an override or a SEV ≤ 3 incident inside the window (doc 07).
7.4 Rollback¶
Rollback is a pointer flip to the prior active ruleset, logged as an action (policy.ruleset.rollback, AUTONOMOUS for ops and the owner, always audited). Runs in flight complete on their pinned version. Proposals already in an approvals inbox are re-resolved under the active ruleset at Act time (doc 06 §4.4); because HUMAN_ONLY and DENY are sticky per decision, a rollback can make a pending item more restrictive but never less.
8. Testing¶
8.1 Unit tests¶
Every rule declares the tests that must pass for activation. Tests are data and run on every lint:
test: T-APR-002-a
rule: R-APR-002
action: spend.approve
scope: { tenant: sim-tidepool, org: ops/support }
ctx:
money: { amount: 4200, currency: USD, category: tooling, vendor_known: true,
envelope: { budget: 20000, spent: 8000 } }
tenant: { policy: { auto_limit: 500, quorum_limit: 25000 }, graduation: { approvals: AUTONOMOUS } }
scores: { blast_radius: 0.31, novelty: 0.05, confidence: 0.86 }
expect:
effect: allow
class: PROPOSE_CONFIRM
winning: [R-APR-002]
constraints_include: [{ kind: route, to: owner }, { kind: expiry, duration: P5D }]
8.2 Property-based tests¶
A generator produces random contexts (from the EvalContext schema), random tenant rulesets (restricted to what product surfaces can produce), and random hard lines. Invariants checked on every generated triple:
| ID | Invariant |
|---|---|
| PB-1 | Determinism: resolve(x) == resolve(x) across processes. |
| PB-2 | Floor: class ≥ floor(action). |
| PB-3 | Monotone narrowing: adding any non-hard tenant rule never lowers the class or loosens a parameter for any context. |
| PB-4 | Hard-line dominance: for any matched hard line H, the decision equals H's clamp regardless of every other rule. |
| PB-5 | HUMAN_ONLY absorption: any matched HUMAN_ONLY rule ⇒ HUMAN_ONLY or DENY. |
| PB-6 | Deny absorption: any matched deny ⇒ DENY. |
| PB-7 | Totality: every registered action type and any context ⇒ a decision, never a throw. |
| PB-8 | Trace completeness: every candidate has exactly one disposition. |
| PB-9 | Obligation monotonicity: adding a rule never removes an obligation. |
| PB-10 | Cost bound: node visits ≤ 64 × candidates. |
8.3 Replay tests¶
Nightly, per tenant, the Active ruleset and any Linted candidate are replayed against the last 30 days of stored PolicyRequests. The request holds the full context, so replay needs no model calls and no integrations. Gates: the active ruleset must reproduce its own stored decisions exactly (drift here is an evaluator bug, SEV2); a candidate's widened bucket must be empty or owner-accepted.
9. Simulation harness¶
The harness answers "what would this rule have done last quarter?"
function simulate(candidate: Ruleset, tenant, from, to):
reqs = receipts.policyRequests(tenant, from, to) # real, stored requests
reqs += simOrg.corpus(tenant.mode) # planted scenarios (EC catalog tests)
report = SimReport()
for req in reqs:
old = req.stored_decision
new = resolve(candidate, req)
if old != new: report.diffs.add(req, old, new, bucket(old, new))
report.humanLoad.add(new) # proposals/week by route, packets/week
report.outreach.add(new) # would-send counts per person per day
report.spend.add(new) # autonomous approvals by amount band
report.hardLineHits.add(new)
report.estimateFatigue(tenant) # against doc 09 caps
return report
The owner sees: decisions changed (three sampled receipts per bucket), projected approvals per week per human, projected outreach per person per day against caps, autonomous money per month, hard-line hits, and — the strongest evidence a widening is unsafe — every decision that would now execute AUTONOMOUS but was in fact overridden by a human when it was PROPOSE_CONFIRM. Thousands of requests resolve in under a second, so simulation runs on every draft save.
Simulation orgs. Two fixture tenants ship with the platform and back every Test column in the edge-case catalog: sim-tidepool (a 42-person software company on Full Suite: Slack, Linear, GitHub, Gusto, Ramp) and sim-kelp-kitchen (a two-location restaurant in Atoms mode: Toast, 7shifts, SMS). Each carries a year of synthetic event log with planted scenarios (stalls, sandbagged goals, an over-threshold spend, a call-out at 05:40, an injection attempt in a ticket description, a PTO clash), referenced by id (SIM-TP-014).
10. Default rule set¶
Layer default unless marked (hard) = platform hard line. "MoR" = manager of record. on_missing is match for deny and HUMAN_ONLY rows, skip otherwise. Where a row gives two outcomes ("A; else B") it compiles to two rules sharing the id prefix.
| ID | Domain | Action | Condition | Effect / class | Constraints | Owner |
|---|---|---|---|---|---|---|
| R-PLAT-001 | platform | hr.terminate, hr.offer.extend, hr.comp.*, hr.promote, hr.discipline.*, pip.initiate, performance.rating.formal, legal.*, safety.decide, shift.dispute.resolve |
always | HUMAN_ONLY (hard) | requires explanation | Chief Security Officer |
| R-PLAT-002 | platform | escalation.suppress, escalation.delay, escalation.reroute_from_mor |
always | DENY (hard) | – | Chief Security Officer |
| R-PLAT-003 | platform | data.read.private_channel, data.read.dm_not_party, data.read.keystroke, data.read.camera, data.read.personal_device |
always | DENY (hard) | – | Chief Security Officer |
| R-PLAT-004 | platform | data.share.cross_tenant, model.train_on_tenant_data, data.retention.override, audit.delete, audit.modify, receipt.modify |
always | DENY (hard) | – | Chief Security Officer |
| R-PLAT-005 | platform | data.read.protected_characteristic; any action with content.refs_protected_characteristic |
always | DENY (hard) | – | Chief Security Officer |
| R-PLAT-006 | platform | any action | content.injection_flag |
DENY (hard) | route ops (quarantine) | Chief Security Officer |
| R-PLAT-007 | platform | * write |
now < tenant.listening_until |
mode shadow (hard); except intro.send, question.ask → AUTONOMOUS, plan.propose.first → PROPOSE_CONFIRM |
rate_limit person/P7D max 3; quiet_hours 20:00–08:00 person | Chonksworth |
| R-PLAT-008 | platform | message.*, content.* |
content.claims_human |
DENY (hard) | – | Chief Marketing Officer |
| R-PLAT-009 | platform | message.* to a person |
person.opted_out contains action type |
DENY (hard) | – | Chief Human Officer |
| R-PLAT-010 | platform | policy.rule.write, policy.ruleset.activate; policy.hardline.write |
always | PROPOSE_CONFIRM; HUMAN_ONLY (hard) | route owner; requires explanation | Chief Security Officer |
| R-PLAT-011 | platform | * write |
always | obligation (hard) | max_blast_radius 0.5 elevate; quorum 2 and route owner when band is critical (doc 06 §5.1) | Chief Security Officer |
| R-PLAT-012 | platform | * write |
scores.novelty first-of-kind (history.same_shape_successes_90d < 3) |
obligation (hard) | max_novelty elevate (AUTONOMOUS → PROPOSE_CONFIRM); global first-of-kind raises E5 informational | Chief Security Officer |
| R-PLAT-013 | platform | * write |
always | obligation (hard) | min_confidence 0.6 elevate; stale inputs with band ≥ medium elevate (doc 06 §5.3–5.4) | Chief Security Officer |
| R-PLAN-001 | planning | plan.rebalance |
not plan.cross_org and not plan.affects_headcount and not plan.affects_budget and plan.org_rebalances_this_week < 2 |
AUTONOMOUS | requires explanation, ic_visibility; max_blast_radius 0.5 elevate | Chief Operating Orca |
| R-PLAN-002 | planning | plan.rebalance |
plan.cross_org or plan.org_rebalances_this_week >= 2 |
PROPOSE_CONFIRM | route MoR (each org); expiry P3D no_action | Chief Operating Orca |
| R-PLAN-003 | planning | plan.change.headcount, plan.change.budget |
always | PROPOSE_CONFIRM | route owner; expiry P10D no_action; quorum 2 if scores.people_affected >= 3 |
Chief Operating Orca |
| R-PLAN-004 | planning | goal.set.quarterly |
always | PROPOSE_CONFIRM | route owner; requires explanation (conflict and sandbag report); expiry P10D no_action | Chief Porpoise Officer |
| R-PLAN-005 | planning | goal.set.personal |
always | PROPOSE_CONFIRM | route ic; ic_visibility; expiry P5D escalate_up (MoR) | Chief Human Officer |
| R-PLAN-006 | planning | project.deadline.move, project.scope.cut |
not plan.external_commitment and plan.deadline_delta_days <= 5 (moves only) |
AUTONOMOUS; else PROPOSE_CONFIRM | requires explanation, ic_visibility; else route project_owner, expiry P3D no_action | Chief Porpoise Officer |
| R-PLAN-007 | planning | capacity.assign |
plan.allocation_delta_pct <= 20 |
AUTONOMOUS; else PROPOSE_CONFIRM | ic_visibility; cooling_off PT24H (an IC objection reverts to PROPOSE); else route MoR + ic | Chief Operating Orca |
| R-PLAN-008 | planning | plan.* |
contradicts an owner standing priority | PROPOSE_CONFIRM | route owner; requires explanation | Chonksworth |
| R-OUT-001 | outreach | brief.send |
always | AUTONOMOUS | rate_limit person/P1D max 1; delivered inside the person's window | Chief Human Officer |
| R-OUT-002 | outreach | checkin.ask |
the question cannot be answered from systems | AUTONOMOUS | rate_limit person/P1D max 1 | Chief Human Officer |
| R-OUT-003 | outreach | chase.internal, chase.cross_org |
always | AUTONOMOUS | rate_limit person/P1D max 3 shared across all Chonks; rate_limit item/PT4H max 1; quiet_hours 20:00–08:00 person; defer while person.on_pto; requires citation (context link) |
Chief Build Officer |
| R-OUT-004 | outreach | chase.vendor |
history.same_shape_successes_90d == 0 for this vendor |
PROPOSE_CONFIRM; else AUTONOMOUS | route MoR, expiry P2D no_action; else rate_limit vendor/P1D max 1, min_confidence 0.7 elevate, approved template only | Chief Build Officer |
| R-OUT-005 | outreach | message.customer |
always | PROPOSE_CONFIRM | route MoR; requires human_edit_window | Head of Revenue |
| R-OUT-006 | outreach | meeting.book |
written_attempts.count >= 1 and hours_since(written_attempts.first_at) >= 24 |
AUTONOMOUS; else DENY | requires reason_in_invite; default 30 min; quiet_hours | Chief Operating Orca |
| R-OUT-007 | outreach | checkin.wellbeing |
always | AUTONOMOUS | rate_limit person/P7D max 1; a second contact on the same signal → PROPOSE_CONFIRM route MoR | Chief Human Officer |
| R-OUT-008 | outreach | message.group.announce |
ritual write-up or celebration | AUTONOMOUS | max_blast_radius 0.5 elevate | Chief Marketing Officer |
| R-APR-001 | approvals | spend.approve |
money.amount <= tenant.policy.auto_limit (default 500) and money.vendor_known and category in plan |
AUTONOMOUS | reversal_window PT1H; requires explanation | Chief Fin Officer |
| R-APR-002 | approvals | spend.approve |
auto_limit < amount <= quorum_limit |
PROPOSE_CONFIRM | route owner; expiry P5D no_action; reversal_window PT1H | Chief Fin Officer |
| R-APR-003 | approvals | spend.approve |
amount > quorum_limit (default 25,000 or > 10% of monthly budget) |
PROPOSE_CONFIRM | quorum 2; route owner; expiry P5D no_action | Chief Fin Officer |
| R-APR-004 | approvals | spend.approve, expense.reimburse |
pct(envelope.spent + amount, envelope.budget) >= 90 |
PROPOSE_CONFIRM | route owner (elevates regardless of amount) | Chief Fin Officer |
| R-APR-005 | approvals | vendor.new |
always | PROPOSE_CONFIRM | route owner; expiry P5D no_action | Chief Fin Officer |
| R-APR-006 | approvals | contract.sign, legal.commit |
always | HUMAN_ONLY | – | Chief Fin Officer |
| R-APR-007 | approvals | expense.reimburse |
amount <= 200 and receipt attached and category in policy |
AUTONOMOUS; else PROPOSE_CONFIRM | reversal_window PT1H; else route MoR, expiry P3D escalate_up | Chief Fin Officer |
| R-APR-008 | approvals | discount.grant; refund.issue |
params.discount_pct <= 10 (discounts); amount <= 100 (refunds) |
AUTONOMOUS; else PROPOSE_CONFIRM | owner hard lines clamp; else route owner | Head of Revenue |
| R-APR-009 | approvals | budget.reallocate |
always | PROPOSE_CONFIRM | route owner; quorum 2 if > 10% of an envelope | Chief Fin Officer |
| R-SCH-001 | scheduling | shift.swap |
both parties consented and len(labor.violations) == 0 and labor.overtime_delta_hours <= 0 |
AUTONOMOUS | ic_visibility (both) | Chief Operating Orca |
| R-SCH-002 | scheduling | shift.callout.fill |
candidate from availability pool, same certification, no violations, no overtime | AUTONOMOUS | quiet_hours waived for SMS when the shift starts in < 12 h; rate_limit person/P1D max 2 asks | Chief Operating Orca |
| R-SCH-003 | scheduling | overtime.assign |
always | PROPOSE_CONFIRM | route MoR (location); expiry PT4H no_action | Chief Operating Orca |
| R-SCH-004 | scheduling | shift.publish |
first 4 published weeks, or labor.notice_days < 7, or any violation |
PROPOSE_CONFIRM; else AUTONOMOUS | route MoR, expiry PT48H escalate_up; else reversal_window PT2H | Chief Operating Orca |
| R-SCH-005 | scheduling | shift.* |
person.is_minor and any exception to youth rules |
HUMAN_ONLY | – | Chief Security Officer |
| R-SCH-006 | scheduling | schedule.reduce_hours |
> 20% below the person's 4-week average | PROPOSE_CONFIRM | route MoR; ic_visibility | Chief Human Officer |
| R-SCH-007 | scheduling | shift.* |
len(labor.violations) > 0 |
DENY | route MoR (solver infeasible) | Chief Security Officer |
| R-FB-001 | feedback | feedback.praise |
always | AUTONOMOUS | requires evidence_links | Chief Human Officer |
| R-FB-002 | feedback | feedback.concern |
history.concerns_same_theme_30d == 0 |
AUTONOMOUS | requires evidence_links, path_forward, ic_visibility; rate_limit person/P7D max 1; min_confidence 0.75 elevate | Chief Human Officer |
| R-FB-003 | feedback | feedback.concern |
history.concerns_same_theme_30d >= 1 |
PROPOSE_CONFIRM | route MoR (human in the room) | Chief Human Officer |
| R-FB-004 | feedback | feedback.share_with_owner |
inside the weekly update | AUTONOMOUS; else PROPOSE_CONFIRM | ic_visibility (symmetry); else route MoR | Chonksworth |
| R-FB-005 | feedback | growth_plan.update |
always | AUTONOMOUS | ic_visibility; IC may edit | Chief Human Officer |
| R-FB-006 | feedback | calibration.adjust |
changes no person's band | AUTONOMOUS; else PROPOSE_CONFIRM | requires explanation; else route MoR | Chief Human Officer |
| R-FB-007 | feedback | feedback.* |
content.compares_named_peers |
DENY | – | Chief Human Officer |
| R-HR-001 | hr | pto.approve |
within policy and coverage OK | AUTONOMOUS | reversal_window PT1H; requires citation | Chief Human Officer |
| R-HR-002 | hr | pto.deny |
always | PROPOSE_CONFIRM | route MoR; expiry P3D escalate_up; never silent | Chief Human Officer |
| R-HR-003 | hr | policy.answer |
topic not in {legal, harassment, safety, medical, immigration, discipline, comp} | AUTONOMOUS; else HUMAN_ONLY | requires citation; else packet to MoR and HR human, the Chonk replies only with the pointer and the policy link | Chief Human Officer |
| R-HR-004 | hr | req.open |
always | PROPOSE_CONFIRM | route owner; expiry P10D no_action | Chief Human Officer |
| R-HR-005 | hr | candidate.screen, candidate.advance |
rubric-only inputs | AUTONOMOUS | requires explanation (scores visible) | Chief Human Officer |
| R-HR-006 | hr | candidate.reject.pre_interview; candidate.reject.post_interview, hr.offer.extend |
always | PROPOSE_CONFIRM; HUMAN_ONLY | route MoR (batched list) | Chief Human Officer |
| R-HR-007 | hr | onboarding.run, ramp_goals.set |
always | AUTONOMOUS | ic_visibility | Chief Human Officer |
| R-HR-008 | hr | hris.write |
field in {address, emergency_contact, self_service} and requested by the IC in-thread; field in {title, comp, status, manager} | AUTONOMOUS; HUMAN_ONLY | requires citation of the IC's message | Chief Security Officer |
| R-HR-009 | hr | leave.protected (parental, medical, statutory) |
always | HUMAN_ONLY (decision); logistics after decision AUTONOMOUS | – | Chief Human Officer |
| R-HR-010 | hr | wellbeing.signal.share |
any recipient other than the IC | PROPOSE_CONFIRM | route MoR; IC notified; never to owner in identifying form without IC consent | Chief Human Officer |
| R-COM-001 | comms | owner_update.send |
weekly cadence | AUTONOMOUS | requires citation (every claim) | Chonksworth |
| R-COM-002 | comms | ritual.writeup.post, celebration.send, content.generate |
within tone dial | AUTONOMOUS | opt-out respected; requires citation | Chief Marketing Officer |
| R-COM-003 | comms | announcement.org_change, allhands.run (strategy content) |
always | PROPOSE_CONFIRM | route owner; requires human_edit_window | Chonksworth |
| R-COM-004 | comms | announcement.departure |
always | PROPOSE_CONFIRM | route owner; requires human_edit_window; DENY if content references a reason | Chief Human Officer |
| R-COM-005 | comms | external.publish |
always | PROPOSE_CONFIRM | route owner; reversal_window PT24H | Chief Marketing Officer |
| R-COM-006 | comms | content.*, message.* |
touches health, comp, or a named person's performance; jokes about any of these | DENY | – | Chief Marketing Officer |
| R-DAT-001 | data | data.read.* |
source in connector allowlist and in the actor's role charter | AUTONOMOUS; else DENY | purpose required; logged | Chief Security Officer |
| R-DAT-002 | data | data.read.comp.individual |
purpose in {human_only.packet, budget.aggregate} | AUTONOMOUS; else DENY | purpose; logged with purpose | Chief Security Officer |
| R-DAT-003 | data | data.export |
always | PROPOSE_CONFIRM | route owner; audited | Chief Security Officer |
| R-DAT-004 | data | memory.write.person |
fact with source | AUTONOMOUS | requires ic_visibility ("what my manager knows") | Chief Security Officer |
| R-DAT-005 | data | memory.write.inference |
health or personality inference | DENY; else AUTONOMOUS | else requires evidence_links, ic_visibility | Chief Human Officer |
| R-DAT-006 | data | connector.scope.expand |
always | PROPOSE_CONFIRM | route owner | Chief Security Officer |
| R-ESC-001 | escalation | escalation.raise.owner |
always | AUTONOMOUS | rate_limit owner/P7D max 3 in the update; immediate ping only at severity ≥ high (doc 09) | Chonksworth |
| R-ESC-002 | escalation | escalation.deliver.ic_initiated |
always | AUTONOMOUS (platform; cannot be clamped by graduation, breakers or kill switch) | deliver ≤ 60 s to MoR | Chief Security Officer |
| R-CAD-001 | cadence | cadence.change (frequency, participants, format) |
always | PROPOSE_CONFIRM | route MoR (ritual owner) | Chief Operating Orca |
| R-CAD-002 | cadence | cadence.skip |
silent | DENY | a missed run raises E1 (doc 09) | Chief Operating Orca |
| R-AGT-001 | agents | agent_worker.assign_task; agent_worker.tool_scope.expand, agent_worker.deprovision |
within the worker's allowlist | AUTONOMOUS; PROPOSE_CONFIRM | route owner | Chief Build Officer |
11. Worked evaluation traces¶
11.1 A chase at 21:10, third message of the day¶
Request: chase.internal from the Chief Build Officer to Priya (org eng/platform, local time 21:10, outreach_today = 2) about a PR unreviewed for 26 hours.
candidates: R-PLAT-006, R-PLAT-007, R-PLAT-008, R-PLAT-009, R-OUT-003
R-PLAT-006 cond_false (injection_flag = false)
R-PLAT-007 cond_false (listening ended 2026-07-14)
R-PLAT-008 cond_false (claims_human = false)
R-PLAT-009 cond_false (opted_out = [])
R-OUT-003 matched
allows: [R-OUT-003] → class AUTONOMOUS
obligations: rate_limit person/P1D max 3 → 2 used, ok
rate_limit item/PT4H max 1 → last chase on item 09:02, ok
quiet_hours 20:00–08:00 person → violated → defer
clamps: graduation[outreach]=AUTONOMOUS, breaker closed, kill_switch 0
decision: ALLOW AUTONOMOUS, mode live, defer_until 2026-09-04T08:00-07:00, winning R-OUT-003
The message is queued, not dropped. At 08:00 the pipeline re-resolves; the PR may have been reviewed overnight, in which case Observe finds no stall and the proposal is withdrawn.
11.2 Over-threshold spend with an org-specific cap¶
Request: spend.approve, $4,200, vendor known, category tooling, org ops/support. Tenant auto_limit is 5,000, but the owner added R-TP-017 (scope org: ops/support, AUTONOMOUS with max_amount 2500 on_violation elevate).
matched: R-APR-001 (4200 <= 5000), R-TP-017; R-APR-002 cond_false
allows by specificity: R-TP-017 (6,3,100) > R-APR-001 (0,3,100)
top: R-TP-017 → AUTONOMOUS; max_amount 2500 violated → elevate → PROPOSE_CONFIRM
params: route owner, expiry P5D no_action, reversal_window PT1H
decision: ALLOW PROPOSE_CONFIRM, winning R-TP-017
Most-specific wins, and the trace shows the owner exactly why the tenant-wide $5,000 did not apply.
11.3 A sub-agent proposes a termination¶
A planner sub-agent, reasoning about a capacity shortfall, emits hr.terminate for a contractor.
R-PLAT-001 matched (hard line) → HUMAN_ONLY, winning R-PLAT-001
pipeline: no human requested this packet; novelty = 1.0 → proposal is NOT turned into a packet;
logged as anomaly; Chief Security Officer raises E5 to Sea Suite ops (doc 07, RISK-01);
planner output discarded.
Even in HUMAN_ONLY the pipeline distinguishes "prepare a packet a human asked for" from "the model spontaneously reached for a human-only lever"; the latter is an anomaly, not a packet (doc 06 §4.3).
11.4 Discount versus a hard line¶
Owner-authored carve-out R-TP-021: discount.grant AUTONOMOUS up to 30% for the Head of Revenue in sales/smb (floor is AUTONOMOUS, so lint accepted it). Owner hard line HL-TIDEPOOL-003: deny discounts above 20%.
request A: discount_pct 15 → HL cond_false; R-TP-021 matched → AUTONOMOUS.
request B: discount_pct 25 → HL matched (deny, hard) → DENY, winning HL-TIDEPOOL-003.
R-TP-021 also matched, traced, irrelevant (P1, P2).
11.5 Listening period¶
Day 4 after onboarding; brief.send to everyone in ops.
R-PLAT-007 matched (now < listening_until; brief.send not whitelisted) → mode shadow
R-OUT-001 matched → AUTONOMOUS
decision: ALLOW AUTONOMOUS, mode shadow → written to the shadow log; the owner sees
"briefs the manager would have sent" in the console.
12. Edge cases¶
| ID | Scenario | Signal / detection | Rule | Action (class) | Escalation | Test |
|---|---|---|---|---|---|---|
| EC-POL-001 | Two tenant rules with identical specificity assign different classes to one action | Lint warning ambiguous_pair; runtime trace shows both matched |
P5 | Most restrictive class applied (n/a) | Owner shown the pair in the diff report | SIM-TP-041: two chase.internal rules for eng |
| EC-POL-002 | Condition needs person.outreach_today but the counter service is down |
ctx_missing in trace; counter-service health metric |
on_missing |
Allow rules skip (DENY if no other allow matches); deny rules match | Ops alert if ctx_missing rate > 1% over 10 min |
Kill the counter service in sim; replay an outreach day |
| EC-POL-003 | Ruleset activated while a run is mid-flight | ruleset_version in receipt differs from active |
Pinning (§6) | Run completes on pinned version (n/a) | None | Activate during a replayed run; assert receipt version stable |
| EC-POL-004 | Owner attempts a hard line lowering hr.terminate to PROPOSE_CONFIRM |
Lint floor_violation |
PB-2, §4.2 | Reject at authoring (n/a) | Owner shown explanation; Chief Security Officer logs the attempt | Author the hard line against sim-tidepool |
| EC-POL-005 | Org lead raises the chase cap for their org above the owner's tenant cap | Lint narrowing_violation |
§3.3 (2) | Reject; owner notified (n/a) | Owner may author the carve-out | SIM-TP-042 |
| EC-POL-006 | A sub-agent emits an unregistered action type | unregistered_action decision |
Totality, §2.3 | DENY; novelty alarm | E5 to Sea Suite ops; model output quarantined | Inject plan.reassign_manager in a planner fixture |
| EC-POL-007 | Person's timezone unknown; quiet-hours rule applies | tz_fallback in trace |
§2.7 | Evaluate in location or tenant tz with the window widened 1 h each side; defer (AUTONOMOUS) | Chief Human Officer asks for the timezone in the next brief | Remove tz from a sim person; chase at 19:30 |
| EC-POL-008 | Time-boxed rule expires while its proposal is pending | Re-resolve at Act returns a different decision | §7.4; doc 06 §4.4 | Proposal re-classified; if now DENY, withdrawn with explanation (n/a) | Approver notified of withdrawal | Set effective.to inside the sim expiry window |
| EC-POL-009 | Evaluation exceeds 500 ms (pathological ruleset) | eval_timeout |
§6 | DENY the instance | E5 to ops | Load a 50,000-rule fixture |
Open questions¶
- Should
org_leadauthority be able to writedenyrules for their own org without owner review? Current answer: yes (it is narrowing), but the owner sees it in the weekly update. Revisit after pilots. - Do we need a fourth layer for a PE operating partner spanning several tenants (portfolio hard lines)? The lattice supports it; the console does not.
- Should
deferhave a maximum hold (a chase deferred past 48 hours is withdrawn rather than sent stale)? Proposed: yes, 48 h, then Observe decides afresh. - Rate-limit accounting across channels (a Slack chase and an SMS to the same person) is currently per person; Atoms pilots may need per-channel budgets.
Related docs¶
- Decision Pipeline — where
resolveis called, scores are computed, and the class is enforced. - Closed-Loop Risk Mitigation — graduation, trust budget, breakers, kill switch.
- Escalation and Human-in-the-Loop — routes, expiries, quorum.
- Edge Case Catalog — the full EC list; EC-POL entries are canonical there too.
- Communication Engine Rules — content lint flags consumed as context.
- Evaluation and Testing — simulation orgs and test infrastructure.
- Data Model and API Reference — full action registry and
EvalContext. - Harness and Orchestration — run pinning and context assembly.