Orchestrata Ultimate / Specs / 034-governed-action-classification

034-governed-action-classification: Governed Action Classification

Replace the constitution's two structural weaknesses — enumerated risk-category name lists and a category-global consent flag — with (a) classification derived from typed properties of the action and the resources it targets, (b) authorization as a scoped grant matched to the specific action instance, from two sources (a human-ratified standing policy rule, or a per-instance human consent grant), and (c) a rule that every allow/deny decision — including the ones made in...

At a glance

Item Value
Spec directory specs/034-governed-action-classification/
Status line in the spec Draft (design for review — NOT approved to build)
Requirement IDs defined 22
With a @traces citation in src/ 0 (0.0%)
With a @traces citation in tests/ 0 (0.0%)
Tracked files 1
RTM page 034-governed-action-classification

Reading this spec

Governed Action Classification is a design for changing how the constitutional governance layer decides whether an action is allowed. The spec identifies two structural weaknesses in the constitution: risk categories are enumerated name lists, which act as a blocklist so that the same operation under a new name passes, and consent is a category-global flag that cannot say "delete this test fixture automatically but never a production table". It also records that allow and deny logic in Python (gateway guards, the kernel dispatcher, the tool interceptor) is either advisory or bypasses the constitution entirely.

The spec replaces these with three things. First, classification derived from typed properties of an action descriptor and a resource descriptor. Second, authorization as a scoped grant matched to the specific action instance, from either a human-ratified standing policy rule or a per-instance human consent grant. Third, a single choke point: every allow or deny decision, including those made in Python today, routes through one Policy Decision Point, the constitutional Prolog gate, and every Python enforcement site becomes a thin Policy Enforcement Point that never decides locally. A single trusted reduction function turns a real operation (tool call, CXP packet, intent, gateway RPC) into the descriptor and asserts the ground facts the rules consume.

The sections cover the action and resource descriptor, property-derived classification, classification provenance (who may say what a resource is, so that an acting agent cannot label its own production delete as a test fixture), scoped authorization, incompleteness (an unknown action does not pass), the amendment direction (what counts as tightening or loosening), the one choke point, a status-honest audit of the current enforcement topology, the build backlog, the threat model and a decision register. Requirement IDs run REQ-034-001 to REQ-034-022, for example REQ-034-018 for the single Policy Decision Point.

No code module cites these requirements, so there are no tracing modules to link. The per-requirement view is on the RTM page.

The spec's status line reads Draft (design for review, not approved to build). It tracks 1 file and defines 22 requirement IDs. The full text follows below.

No src/ symbol cites any of the 22 requirements, so measured trace coverage is 0.0%. In tests/, 0 requirements are cited by a test-side @traces.

Coverage gaps: 22 requirements have no citing symbol. The largest untraced groups are REQ-034-* (22). Untraced means no @traces names the ID; the behaviour may still exist in code that was never annotated.

Requirements

Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.

ID Done Requirement Traced by
REQ-034-001 - An Action is a typed descriptor, not an atom: none
REQ-034-002 - A Resource is a typed descriptor: none
REQ-034-003 - Descriptors are modelled in the canonical KG ontology first (the project's none
REQ-034-004 - Each risk category is defined by the properties that constitute it, not by a none
REQ-034-005 - The category taxonomy (defining properties, and is-a subtype edges such as none
REQ-034-006 - A category rule is testable against properties: a regression asserts a none
REQ-034-007 - A resource's classification attributes (class, env, owner, sensitivity) none
REQ-034-008 - Where classification cannot be read from structure, the order of trust is none
REQ-034-009 - A verified test asserts the agent uid cannot relabel none
REQ-034-010 - An action is authorized iff a grant's **selector matches this action instance's none
REQ-034-011 - A standing policy grant is a human-ratified pattern rule that auto-authorizes none
REQ-034-012 - A per-instance consent grant is bound to a specific resource (optionally its none
REQ-034-013 - Article I (and the other consent-gated articles) consume authorized/1: none
REQ-034-014 - none
REQ-034-015 - An action whose descriptor lacks an attribute a category rule needs is none
REQ-034-016 - This is the safety net for ADR-0034's residual blocklist hole: property-derived none
REQ-034-017 - Under ADR-0009 / ADR-0020, for SPEC-034 artifacts: none
REQ-034-018 - Single PDP.** There is exactly one Policy Decision Point: the constitutional / none
REQ-034-019 - No pure-Python policy.** A Python allow/deny that decides without consulting the none
REQ-034-020 - Single reduction function (Python supplies the atoms).** One trusted-side function none
REQ-034-021 - Audit-only is not enforcement.** Where the kernel dispatcher currently runs the none
REQ-034-022 - Fail-closed reduction.** If reduce_to_action cannot classify an operation none

Code that traces this spec

No src/ symbol cites a requirement of this spec.

Full spec text

Verbatim from specs/034-governed-action-classification/spec.md; headings are pushed down two levels.

SPEC-034: Governed Action Classification

Metadata

Nature of this spec (read first). Prescriptive target, not documentation of existing code. Unbuilt components are planned tasks (§9), not defects. It is a design put up for ratification — the constitution and its authorization model are constitution-tier, so nothing here is built until Mohamed accepts the three ADRs (§11) that back it.

docs/adr/ is authoritative for rationale. The three decisions this spec rests on are ADR-0034 (property-derived classification), ADR-0035 (two-source scoped authorization), ADR-0036 (classification provenance under the trust boundary). Where this spec and an ADR disagree, the ADR wins. References are inline as [ADR-NNNN].


Overview

What

Replace the constitution's two structural weaknesses — enumerated risk-category name lists and a category-global consent flag — with (a) classification derived from typed properties of the action and the resources it targets, (b) authorization as a scoped grant matched to the specific action instance, from two sources (a human-ratified standing policy rule, or a per-instance human consent grant), and (c) a rule that every allow/deny decision — including the ones made in Python today — routes through the one constitutional choke point, which is supplied its atoms from a single, trusted reduction function.

Why
How

An Action descriptor and a Resource descriptor (typed, in the canonical ontology) are the only thing the gate reasons over. A single reduction function on the trusted side turns a real runtime operation (tool call, CXP packet, intent, gateway RPC) into that descriptor and asserts the derived ground facts the rulebase consumes. The constitution classifies by property, and composes the classification with a scoped-grant authorization check. Every Python enforcement point becomes a thin Policy Enforcement Point (PEP) that calls the one Policy Decision Point (PDP) — it never decides locally.


1. The action / resource descriptor (the thing being classified)

REQ-034-001 — An Action is a typed descriptor, not an atom: Action{ id, verb, actor, targets:[ResourceRef], attributes:{...}, context }. verb is drawn from a closed, ontology-defined verb set (delete, deploy, read, execute, modify_policy, terminate_agent, ...); free-form verbs are unknown and route to incompleteness (§5), never to allow.

REQ-034-002 — A Resource is a typed descriptor: Resource{ id, class, env, owner, reversibility, sensitivity, provenance, version }. class and env are closed enumerations in the canonical ontology (canonical_schema.py, SPEC-106) — e.g. env ∈ {prod, staging, test, ephemeral}, class ∈ {user_data, system_config, governance_asset, test_fixture, build_artifact, ...}.

REQ-034-003 — Descriptors are modelled in the canonical KG ontology first (the project's ontology-before-import rule), and the constitution reasons over a Prolog projection of them. The .pl files hold rules over properties, not rosters of names.

2. Property-derived classification (replaces the name lists) [ADR-0034]

REQ-034-004 — Each risk category is defined by the properties that constitute it, not by a list of atoms:

%% BEFORE (rejected):  security_threat(bypass_auth).   security_threat(execute_untrusted).
%% AFTER:
security_threat(A) :- action_property(A, untrusted_execution).
security_threat(A) :- action_property(A, auth_bypass).
security_threat(A) :- action_property(A, audit_disable).
destructive_change(A) :- action_target(A, R), resource(R, reversibility, irreversible),
                         \+ authorized(A).

REQ-034-005 — The category taxonomy (defining properties, and is-a subtype edges such as code_injection ⊑ untrusted_execution ⊑ security_threat) lives in the ontology. Adding a subtype or a defining property is a governance change subject to §6 amendment direction.

REQ-034-006 — A category rule is testable against properties: a regression asserts a descriptor bearing property P is classified, and one lacking every defining property is not — the current lists have no such test because membership is definitional.

3. Classification provenance — who gets to say what a resource is [ADR-0036]

REQ-034-007 — A resource's classification attributes (class, env, owner, sensitivity) are written by its creator at creation time under the trusted-side identity, and are immutable to the acting agent identity (ADR-0018 boundary). The test harness stamps class=test_fixture, env=test, owner=<test_run> when it creates a fixture; a production resource is classified by its schema/migration/declaring system.

REQ-034-008 — Where classification cannot be read from structure, the order of trust is structural → detector-flagged-then-adjudicated → human (ADR-0029 high-recall detector, never a silent verdict; ADR-0014 promotes the adjudication into the KB). An agent's self-description is never a classification source (ADR-0008).

REQ-034-009 (acceptance, red-team) — A verified test asserts the agent uid cannot relabel a production resource as a test fixture by any route (KG write, Prolog assertz, descriptor field, env var). Until the ADR-0018 identity boundary is real (SPEC-033 B1d), this test is the gate on SPEC-034's enforcement claims — a classification store the agent can write makes the whole model cosmetic.

REQ-034-010 — An action is authorized iff a grant's selector matches this action instance's attributes. authorized/1 composes two sources:

authorized(A) :- auto_authorized(A).          %% standing policy grant (human-ratified pattern)
authorized(A) :- matching_consent_grant(A).   %% per-instance human grant, unconsumed & unexpired

REQ-034-011 — A standing policy grant is a human-ratified pattern rule that auto-authorizes a scoped class with no per-instance human touch. The motivating case is expressed directly:

auto_authorized(A) :-
    action(A, verb, delete), action_target(A, R),
    resource(R, class, test_fixture), resource(R, env, test).

Adding or widening such a rule is a loosening ⇒ human ratification (ADR-0009); the B16 amendment classifier flags it.

REQ-034-012 — A per-instance consent grant is bound to a specific resource (optionally its content version), is single-use (scope=once), and carries provenance and expiry: consent_grant(GrantId, who, verb, target, scope, version, reason, at). It is consumed on use (the grant→action binding recorded in the B1c ledger / B20 attestation), and a version-bound grant stops matching once the resource content changes.

REQ-034-013 — Article I (and the other consent-gated articles) consume authorized/1: requires_human_consent(A), \+ authorized(A) → deny. The category-global human_consent_given/2 flag is removed.

REQ-034-014 (acceptance — the worked example, ratifiable):

5. Incompleteness — the unknown action does not pass [ADR-0011]

REQ-034-015 — An action whose descriptor lacks an attribute a category rule needs is unclassified, and an unclassified sensitive action escalates, it does not default-allow. A verb ∉ ontology or a class = unknown on a targeted resource is an incompleteness signal routed to the B13 assumption/adjudication cascade, not a silent permit.

REQ-034-016 — This is the safety net for ADR-0034's residual blocklist hole: property-derived classification still can't catch a property nobody modelled, but the absence of a classification on a sensitive resource is itself detectable and denied-pending-adjudication.

6. Amendment direction (what's a tightening vs a loosening)

REQ-034-017 — Under ADR-0009 / ADR-0020, for SPEC-034 artifacts:

7. One choke point: Python allow/deny routes through the constitution

This section answers Mohamed's 2026-07-24 directive: "how python code that allows and denies must also go through the constitutional layer and possibly create atoms that supply the pl code." The current-state topology (which sites route through Prolog, which are audit-only, which bypass) is enumerated in §8 from the code audit; this section states the target.

REQ-034-018 — Single PDP. There is exactly one Policy Decision Point: the constitutional / governance Prolog gate. Every allow/deny in the system is a Policy Enforcement Point (PEP) that (1) builds no verdict of its own, (2) reduces its operation to an Action descriptor via the single reduction function (REQ-034-020), (3) calls the PDP, (4) enforces the returned verdict. A PEP contains no policy — no allowlist, no denylist, no hardcoded if action == ....

REQ-034-019 — No pure-Python policy. A Python allow/deny that decides without consulting the PDP is a governance bypass and is a defect regardless of authorship (§8 lists the current ones). Permitted Python-local rejects are limited to non-policy failures (malformed input, transport error, auth identity failure) — these deny-closed and are distinct from a policy verdict. A Python site may pre-reduce and cache, never decide.

REQ-034-020 — Single reduction function (Python supplies the atoms). One trusted-side function reduce_to_action(op) -> ActionDescriptor turns each operation kind — tool call (ToolInterceptor / claude hook), CXP packet (kernel dispatcher), intent (orchestrator), gateway RPC (policy gate / scope guard) — into the typed descriptor, and one assert_action_facts(desc) projects it to the ground Prolog facts the rulebase reads (action/3, action_target/2, action_property/2, resource/3). This is the only place atoms are minted for the gate. Consequences:

REQ-034-021 — Audit-only is not enforcement. Where the kernel dispatcher currently runs the PolicyEvaluator in AUDIT-ONLY mode (records the verdict, does not act on it), promotion to enforcing is a governance change: fail-closed on deny, with the SPEC-033 liveness posture (interactive sessions fail-open, factory gates fail-closed — ADR-0002) preserved. The shadow → enforce transition is itself ratified.

REQ-034-022 — Fail-closed reduction. If reduce_to_action cannot classify an operation (unknown verb, unresolvable target), the PEP treats it as §5 incompleteness — escalate/deny pending adjudication in a factory context; in an interactive context, the ADR-0002 fail-open applies but the unclassified action is logged as a governance gap.

8. Current-state enforcement topology (audit — status-honest)

Populated from the code audit. Each site is classed routes-through-Prolog (PDP-backed), audit-only (advisory), or pure-Python bypass (decides without the PDP). Bypasses and audit-only sites are the SPEC-034 backlog in §9; this is a map of what exists, not a list of defects to fix silently.

Structural finding (the load-bearing one). There is no reduction function mapping a real runtime operation to the constitution's atom vocabulary. constitutional_check/3 reasons over a hardcoded vocabulary in constitutional.pl (requires_human_consent/1 L73-79, involves_pii/1 L169-171, security_threat/1 L232-236, destructive_change/1 L299-301). The "Action atom" it sees is the raw action string the caller happens to pass, formatted by prolog_client._format_atom (L186). Unless a caller literally passes action="delete_data", the check matches the permissive default clauses (Articles VIII–X are bare true; accountable_operation is always true) and never denies. So the constitution is, in practice, structurally incapable of firing on real actions — exactly the ADR-0034 "the reduction is undefined" hazard, now demonstrated. This is the single highest-value SPEC-034 target (B34-5).

Three parallel, weakly-connected rulebases exist: constitutional.pl (via PolicyEngine), governance.pl evaluate_policy/3 + may_execute/2 (via the orchestrator engine and GovernancePrologClient), and policy_evaluator.py policy_permits/4/tool_allowed/4 (audit-only). Each builds its terms from raw strings independently — the "scattered, per-rulebase reduction" the single reduction function (REQ-034-020) replaces.

Routes through Prolog (PDP-backed, authoritative)
Audit-only (Prolog runs; verdict never blocks)
Pure-Python bypass (never consults governance/Prolog) — the SPEC-034 backlog
Fail-open / error-allow holes (deny-closed elsewhere, open here)
Atom supply today (who asserts ground facts to the constitution)

9. Build Status & Implementation Backlog (tasks, not gaps)

Nothing in this section is built. This spec is for ratification first. On acceptance, the backlog is:

10. Threat model & failure posture

11. Decision register

All three Proposed, awaiting human ratification (constitution-tier). SPEC-034 is not built until they are accepted.