Skip to content

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

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:

  1. 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 resolve regardless of what the model believes.
  2. Every decision is replayable. Receipts carry the pinned ruleset_version and the exact context; resolve over a stored request reproduces the stored decision or the test suite fails.
  3. 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

  1. Against the floor. Any rule at any layer: class ≥ floor(action). Lint error otherwise.
  2. 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.
  3. 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_version at 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 resolve in-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, reason eval_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:

  1. 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.
  2. Dual evaluation. Both rulesets evaluate every request; the old one is enforced; every disagreement is logged as policy.shadow_diff with both traces. Promotion requires the widened count 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

  1. Should org_lead authority be able to write deny rules 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.
  2. 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.
  3. Should defer have a maximum hold (a chase deferred past 48 hours is withdrawn rather than sent stale)? Proposed: yes, 48 h, then Observe decides afresh.
  4. 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.