diff --git a/docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md b/docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md index 6e2c5d8110..e8bb806c3b 100644 --- a/docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md +++ b/docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md @@ -33,6 +33,10 @@ consolidates the normative rules from this ADR and subsequent implementation changes into a single reference for dispatch implementations, forge adapters, and harness authors. +[ADR 0098](0098-entity-first-harness-evaluation.md) extends this decision for +Fullsend-originated entity discovery without a prompting event. Event-backed +dispatch remains subject to this ADR's actor authorization gate. + ## Context The dispatch routing logic (`dispatch.yml` / `reusable-dispatch.yml`) diff --git a/docs/ADRs/0061-harness-cel-dispatch.md b/docs/ADRs/0061-harness-cel-dispatch.md index a2fa335e36..79529836f2 100644 --- a/docs/ADRs/0061-harness-cel-dispatch.md +++ b/docs/ADRs/0061-harness-cel-dispatch.md @@ -21,6 +21,11 @@ Accepted (partially supersedes static stage routing from [ADR 0041](0041-synchronous-workflow-call-event-dispatch.md); preserves its synchronous `workflow_call` execution model) +Only the event-only CEL context is superseded by +[ADR 0098](0098-entity-first-harness-evaluation.md), which retains one harness +predicate but evaluates it against a resolved entity and optional event. The +remaining decisions in this ADR remain accepted. + ## Context Custom agents should be easy to author and portable across forges. Today, adding diff --git a/docs/ADRs/0063-polling-based-work-discovery.md b/docs/ADRs/0063-polling-based-work-discovery.md index a1e115297a..47da8e9b07 100644 --- a/docs/ADRs/0063-polling-based-work-discovery.md +++ b/docs/ADRs/0063-polling-based-work-discovery.md @@ -22,6 +22,11 @@ Date: 2026-06-18 Accepted +The requirement for poll drivers to reconstruct changes as events is +superseded by [ADR 0098](0098-entity-first-harness-evaluation.md). The poll +command, driver architecture, per-repo scope, and coordination decisions remain +current. + ## Context Fullsend's primary dispatch path is **event-driven**: forge webhooks are normalized diff --git a/docs/ADRs/0098-entity-first-harness-evaluation.md b/docs/ADRs/0098-entity-first-harness-evaluation.md new file mode 100644 index 0000000000..e6c48df53e --- /dev/null +++ b/docs/ADRs/0098-entity-first-harness-evaluation.md @@ -0,0 +1,161 @@ +--- +title: "98. Evaluate harnesses against entities with optional event context" +status: Accepted +relates_to: + - agent-architecture + - agent-infrastructure +topics: + - agents + - cel + - dispatch + - entities + - polling +--- + +# 98. Evaluate harnesses against entities with optional event context + +Date: 2026-09-03 + +## Status + +Accepted + +Partially supersedes [ADR 0061](0061-harness-cel-dispatch.md): a +`NormalizedEvent` is no longer the sole CEL input to a harness trigger. ADR +0061's remaining dispatch decisions remain current. + +Partially supersedes [ADR 0063](0063-polling-based-work-discovery.md): polling +no longer has to reconstruct changes as `NormalizedEvent` values. ADR 0063's +poll command, driver architecture, per-repo scope, and coordination decisions +remain current. + +Extends [ADR 0054](0054-require-authorization-on-all-agent-dispatch-paths.md) +with a scoped authorization path for Fullsend-originated entity discovery that +has no prompting event actor; event-backed dispatch authorization is unchanged. + +## Context + +[ADR 0061](0061-harness-cel-dispatch.md) made a `NormalizedEvent` the sole CEL +input to a harness trigger, and ADR 0063 extended that model to polling by +reconstructing entity changes as events. That fits transition-oriented rules, +but state-oriented agents must recover complete event history to answer durable +questions such as whether an authorized `/fs-fix` comment remains unhandled or +an issue needs periodic reconsideration +([#313](https://github.com/fullsend-ai/fullsend/issues/313), +[agents#1137](https://github.com/fullsend-ai/agents/issues/1137)). + +Event reconstruction loses source-specific fidelity, consumes API capacity, and +can permanently miss work across checkpoint gaps. Conversely, replacing events +would discard useful transition and actor context. Harness authors need one rule +that works when either a live event or scheduled discovery prompts evaluation. + +## Options + +### Continue reconstructing events for polling + +One trigger shape remains simple, but poll drivers must recover ordered history +and a missed transition may never be reconsidered. + +### Add separate event and entity predicates + +Each path is explicit, but authors must keep two routing rules consistent and +the two rules may disagree about the same entity. + +### Use one predicate over an entity and optional event + +Both paths share one decision rule; entity resolution and durable processing +state become platform responsibilities. + +## Decision + +- **Predicate context:** Adopt one harness CEL predicate evaluated with a + required forge-neutral `entity` and a nullable `event`. The predicate may + inspect either or both. Harnesses that declare entity sources and can + therefore be evaluated without a prompting event MUST test `event != null` + before accessing event fields. This test provides crash safety only; a + harness intended to match during scheduled discovery MUST also express an + entity-state condition that can succeed when `event` is null. Harnesses + without entity sources remain event-triggered only, always receive an event, + and need no compatibility change to existing event-based predicates. Trigger + CEL represents a missing event as CEL null. Overlay `when` expressions also + receive the required `entity` and nullable `event`. The normalized entity + MUST expose its source system as `entity.source.system`; overlays used for + entity discovery select source-specific configuration from that field rather + than `event.source.system`. +- **Candidate sources:** Event-driven dispatch resolves the event's entity and + supplies both values; scheduled discovery supplies the entity with `event` + set to null. Events are a low-latency source of candidates, not the + authoritative representation of whether an entity still needs work. +- **Entity resolution:** Harnesses declare entity sources that enumerate + candidate identities and resolve an identity to its current normalized + representation. Fullsend MAY combine compatible sources into shared provider + queries, but each harness defines its own predicate and handled-state test. + `fullsend poll` and its pluggable drivers remain valid mechanisms for + scheduled enumeration and resolution. +- **Handled-state evidence:** The normalized entity contract MUST provide + stable cross-system identity, current state, the bounded or queryable + activity required by the harness, and actor context. An action-indicating + element is actor-originated entity history whose content or state is treated + as a request for a stage to run, including issue or change-proposal bodies, + comments, reviews, and label applications when used for that purpose. Every + such element MUST expose actor provenance and the actor's current forge + permission level resolved at entity evaluation time. A harness MAY infer that + qualifying activity has already been handled from entity state, such as an + existing triage comment, or use an explicit per-harness receipt or poll + checkpoint. The field-level contract and query-planning protocol belong in a + versioned normative specification and MUST exist before entity-first + execution is enabled. +- **Scheduling:** Recurring evaluation MAY be initiated by a platform/default + clock or constrained by scheduling metadata in the harness. The clock is + scheduling machinery, not an authorization principal. Scheduled entity + discovery is denied unless the harness opts in through valid entity sources + and effective platform policy permits it. Missing or malformed entity sources + deny scheduled evaluation for that harness; missing, malformed, or + unverifiable platform eligibility policy denies every scheduled evaluation + governed by that policy. The effective schedule may still come from platform + defaults or harness metadata. Every resulting run uses the harness's + configured agent identity and permissions. +- **Authorization:** [ADR 0054](0054-require-authorization-on-all-agent-dispatch-paths.md) + continues to authorize event-backed dispatch from its event actor. A + `fullsend poll` entity-discovery run is instead authorized by its trusted + Fullsend-controlled origin; callers that cannot establish that provenance are + denied. Entity history remains untrusted input. For each candidate harness, + before evaluating its CEL predicate, the platform MUST enumerate a + platform-defined closed superset of action-indicating elements for the entity + kind and remove those whose actor's current permission does not meet that + harness's observation or mutation threshold. Failed, unavailable, or + incomplete enumeration denies evaluation of that entity; only successful + enumeration may produce an empty set for state-only predicates. CEL can + additionally restrict action using the retained elements and their current + actor permissions, but does not select an element for a later authorization + gate. Further trust and injection filtering MAY run after CEL routing and + before the harness pre-script. Neither entity content nor a historical actor + can alter the run's configured identity or permissions. +- **Attribution:** A scheduled run without a prompting human is attributable to + the verified Fullsend invocation identity, target repository, effective + platform policy, harness revision, and resolved entity. The audit record also + identifies every retained action-indicating element and its actor. For + state-only predicates such as staleness, the configured service identity and + versioned policy/harness configuration are the initiating authority. + +## Consequences + +- Harness authors maintain one predicate across event and polling contexts. + Harnesses opting into entity sources must guard event access with + `event != null`, and those intended to match during scheduled discovery must + include an entity-state condition that can succeed when `event` is null. +- Poll drivers can discover current actionable state without reconstructing a + complete synthetic event stream. +- Harnesses may reuse entity state as handled-state evidence instead of writing + separate receipts or checkpoints. +- Efficient cross-entity enumeration may still require provider-specific + indexing beyond a forge's native query API; resolution and persistence do not + necessarily require new infrastructure. +- Event actors and historical activity remain usable in CEL without weakening + the centralized authorization boundary. +- Existing source-specific overlay expressions that inspect + `event.source.system` must use `entity.source.system` for entity-discovery + runs. +- Existing event-only harnesses remain compatible; harnesses opt into nullable + event context by declaring entity sources. ADR 0063's event-reconstruction + path still requires a migration plan for polling drivers. diff --git a/docs/architecture.md b/docs/architecture.md index 7196ffad61..b680584bb8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -149,9 +149,13 @@ repo baseline and overrides) Branch-targeting logic lives in the portable post-script, not in workflow YAML ([ADR 0053](ADRs/0053-agent-driven-branch-targeting.md)). - Harness trigger expressions: each harness may declare an optional CEL - `trigger` boolean evaluated against a forge-neutral `NormalizedEvent`. - `fullsend dispatch` matches events to harnesses via input/output drivers - ([ADR 0061](ADRs/0061-harness-cel-dispatch.md)). + `trigger` boolean evaluated against a required forge-neutral normalized + entity and an optional prompting `NormalizedEvent`. `fullsend dispatch` + resolves each event's entity before matching; scheduled discovery evaluates + resolved entities with no prompting event. Harnesses without entity sources + remain event-triggered only and may rely on the event being present + ([ADR 0061](ADRs/0061-harness-cel-dispatch.md), partially superseded by + [ADR 0098](ADRs/0098-entity-first-harness-evaluation.md)). - Portable provider and profile resolution: provider and profile definitions can be URL-referenced (sha256-pinned) or specified as local file paths in the harness, enabling portable base harnesses that carry their own @@ -283,38 +287,62 @@ The existing design principle is that [the repo is the coordinator](problems/age - Routing moves from workflow bash to harness CEL `trigger` expressions evaluated by `fullsend dispatch` with pluggable input/output drivers operating on a `NormalizedEvent` struct - ([ADR 0061](ADRs/0061-harness-cel-dispatch.md)). + ([ADR 0061](ADRs/0061-harness-cel-dispatch.md), partially superseded by + [ADR 0098](ADRs/0098-entity-first-harness-evaluation.md)). - Per-repo **polling** complements webhook dispatch: `fullsend poll` uses poll input drivers to discover work from remote systems (Jira first), coordinates via source-native write-then-verify locks, and feeds the same dispatch pipeline as webhooks ([ADR 0063](ADRs/0063-polling-based-work-discovery.md)). Initial scope is per-repo mode only. +- Harness routing uses one CEL predicate over a required normalized entity and + an optional prompting event. Webhooks provide low-latency candidates; + `fullsend poll` and its input drivers enumerate and resolve scheduled + candidates without reconstructing a complete event stream. Entity activity + retains actor and authorization provenance, while harness-defined evidence — + existing entity activity or explicit receipts — distinguishes handled work + ([ADR 0098](ADRs/0098-entity-first-harness-evaluation.md), partially + superseding [ADR 0063](ADRs/0063-polling-based-work-discovery.md)). - GitLab dispatch uses cron-polled scheduled pipelines for issue/comment/label events, MR-open review, and MR-merge retro. Native `merge_request_event` no-ops review (protected variables are unavailable on MR refs). No webhook bridge required (see [ADR 0067](ADRs/0067-gitlab-cron-polling-event-dispatch.md)). - Conversation participation: GitHub Discussions (and future chat systems) enter - dispatch as `NormalizedEvent` entities with `entity.kind: conversation`, - express threading on `transition.comment.id` / `parent_id` (`parent_id` always - names the thread root), reuse CEL harness triggers and ADR 0054 authorization, - and write back through host/post-script or host-side API servers via - `conversation.Client` — not a separate always-on chat bot and not an - extension of `forge.Client` + dispatch as resolved entities with `entity.kind: conversation`; when a + prompting event is available, it expresses threading on + `transition.comment.id` / `parent_id` (`parent_id` always names the thread + root). They reuse CEL harness triggers and ADR 0054 authorization, and write + back through host/post-script or host-side API servers via + `conversation.Client` — not a separate always-on chat bot and not an extension + of `forge.Client` ([ADR 0086](ADRs/0086-conversation-surface-for-agent-participation.md)). -- Dispatch authorization gate: all agent dispatch paths — slash commands - and automatic event triggers — require authorization before dispatching. - GitHub paths check the acting user's collaborator permission via the - repository API (`write` or above for mutation commands; `triage` or above - for observation stages). Non-GitHub dispatch paths (e.g., Jira polling) - map source-system roles to dispatch authorization roles (`read`, `write`, - `admin`) using source-native role resolution; the resolved role feeds the - same authorization gate with no cross-system identity verification +- Event-backed dispatch authorization: event-triggered paths authorize the prompting + actor before dispatch. This includes schedule/manual dispatch represented as + a `NormalizedEvent`, whose actor is the configured service identity. GitHub + paths check the acting user's collaborator permission via the repository API + (`write` or above for mutation commands; `triage` or above for observation + stages). Non-GitHub event paths map source-system roles to dispatch + authorization roles (`read`, `write`, `admin`) using source-native role + resolution, with no cross-system identity verification ([Authorization Contract v1](normative/authorization/v1/); [ADR 0054](ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md)). +- Poll entity-discovery authorization: `fullsend poll` has no prompting event + actor; verified, non-user-assertable Fullsend invocation provenance authorizes + entity enumeration and evaluation, and callers without it are denied. Before + each candidate harness's CEL predicate, the platform fail-closed enumerates a + closed superset of action-indicating entity elements, resolves each actor's + current permission, and removes elements below that harness's observation or + mutation threshold. Later input-selection and injection filtering are defense + in depth for prompt construction. Entity-first execution remains disabled + until its versioned normalized-entity contract exists. Every run uses the + harness's configured identity + ([ADR 0098](ADRs/0098-entity-first-harness-evaluation.md)). **Open questions:** +- What normative entity-history, query-planning, and handled-state contract + can support entity-first harness evaluation without unbounded provider reads + ([ADR 0098](ADRs/0098-entity-first-harness-evaluation.md))? - Is GitHub's event system sufficient for forge-native duplicate protection, or do we need additional coordination beyond label/state conventions and agent idempotency? (Jira polling per ADR 0063 uses entity-property locks and runner - lock refresh.) + lock refresh; ADR 0098 does not resolve this question.) - How does work assignment interact with the backlog/priority agent described in [agent-architecture.md](problems/agent-architecture.md)? - What happens when work needs to be cancelled, retried, or reassigned? - Does the coordinator need state (a queue, a lock, a claim system), or can it be stateless and event-driven? @@ -370,6 +398,12 @@ Observability is a cross-cutting concern that touches every other component. Eac - JSONL reasoning trace exposure: raw JSONL conversation transcripts are extracted from sandboxes and stored with owner-scoped access. Credential scanning acts as an invariant check on [ADR 0017](ADRs/0017-credential-isolation-for-sandboxed-agents.md)'s isolation model. Agents handling data from protected sources beyond the target repo can opt in to JSONL suppression via configuration ([ADR 0021](ADRs/0021-jsonl-reasoning-trace-exposure.md)). - Event-driven stage dispatch remains traceable end-to-end in the GitHub Actions UI by using synchronous `workflow_call` dispatch (see [ADR 0041](ADRs/0041-synchronous-workflow-call-event-dispatch.md)). +- Scheduled entity-discovery runs are attributable to the verified Fullsend + invocation identity, target repository, effective policy, harness revision, + and resolved entity; retained action-indicating elements preserve their actor + provenance. State-only predicates trace to the configured service identity + and versioned policy/harness configuration + ([ADR 0098](ADRs/0098-entity-first-harness-evaluation.md)). - Distributed tracing: framework-native OpenTelemetry instrumentation with zero-configuration baseline. Every run produces `run-telemetry.jsonl` locally; optional live OTLP export to any compatible backend. W3C trace context propagation links multi-agent pipelines into unified traces. OTEL GenAI semantic conventions enable LLM-aware backends ([ADR 0050](ADRs/0050-distributed-tracing-instrumentation.md)). - Eval measurements: the concept of scoring traces ([fail-open](glossary.md#fail-open)). [OTEL primary facts](glossary.md#otel-primary-facts) stay on the run trace (`run-telemetry.jsonl`); [OTEL derived products](glossary.md#otel-derived-products) are the scores (`eval-measurements.jsonl`) ([ADR 0087](ADRs/0087-eval-measurements-online-trace-scoring.md)). See [Eval Measurements](guides/infrastructure/eval-measurements.md). When `OTEL_EXPORTER_OTLP_*` is set, scores also export as `gen_ai.evaluation.result` span events on the same TraceID (same OTLP path as agent traces; fail-open). @@ -379,7 +413,12 @@ Observability is a cross-cutting concern that touches every other component. Eac - ~~How do we balance detailed tracing (useful for debugging) with the volume of data agents will produce?~~ Decided in [ADR 0050](ADRs/0050-distributed-tracing-instrumentation.md): instrument all lifecycle steps comprehensively; volume is managed by backends not by suppressing data at the source. - ~~How do we score wild agent traces for trends without a second export stack?~~ Decided in [ADR 0087](ADRs/0087-eval-measurements-online-trace-scoring.md): eval measurements write local JSONL beside telemetry when at least one new score row is produced (including `label: skip`); portable remote export uses the same OTLP config as traces (`gen_ai.evaluation.result` events). The JSONL is absent (not empty) when telemetry/manifest is missing, no traces match, or every candidate is already in the ledger. - What is the retention and access model for agent logs? Who can see what? (JSONL trace access model decided in [ADR 0021](ADRs/0021-jsonl-reasoning-trace-exposure.md); retention policy and broader log access remain open.) -- How does observability interact with the security requirement that "every action is logged, attributable, and reviewable"? (See [security-threat-model.md](problems/security-threat-model.md).) +- How does observability interact with the security requirement that "every + action is logged, attributable, and reviewable"? Scheduled entity-discovery + attribution is decided in + [ADR 0098](ADRs/0098-entity-first-harness-evaluation.md); broader audit-log + requirements remain open. (See + [security-threat-model.md](problems/security-threat-model.md).) - Is there a real-time monitoring requirement (agent is stuck, agent is behaving anomalously), or is observability primarily forensic? ## Agent Registry diff --git a/docs/normative/authorization/v1/README.md b/docs/normative/authorization/v1/README.md index 7599bcaf05..1396d9f137 100644 --- a/docs/normative/authorization/v1/README.md +++ b/docs/normative/authorization/v1/README.md @@ -98,6 +98,10 @@ The bash dispatch implementation uses a parameterized The authorization gate is **fail-closed**: when a role cannot be determined, the actor is denied. +The entity-discovery rows below specify the future ADR 0098 path and do not +describe behavior currently implemented by `fullsend dispatch` or +`fullsend poll`. + | Condition | Outcome | |-----------|---------| | Collaborator API returns an unrecognized `role_name` | Mapped to `none`; denied | @@ -105,6 +109,14 @@ determined, the actor is denied. | Custom repository roles (GitHub) | Mapped to `none`; denied until custom roles are handled platform-wide | | `actor.role` is empty or missing | Event fails `NormalizedEvent` validation; never reaches dispatch | | Username is empty | Denied | +| Fullsend poll invocation provenance is missing or unverifiable | Entity discovery denied | +| Harness entity sources are missing or malformed | That harness is skipped for scheduled evaluation | +| Effective platform eligibility policy is missing, malformed, or unverifiable | Scheduled evaluations governed by that policy are denied | +| Effective platform eligibility policy uses a wildcard without explicit platform-level justification | Scheduled evaluations governed by that policy are denied | +| Action-indicating enumeration fails, is unavailable, or does not cover the entity kind | Evaluation of that entity is denied | +| Current permission for an action-indicating element's actor is missing or unverifiable | That element cannot trigger execution | +| Current permission for an action-indicating element's actor is below the applicable stage threshold | That element cannot trigger execution | +| Versioned normalized-entity contract is missing or incomplete | Entity-first execution denied | ## Exceptions @@ -153,6 +165,51 @@ permission of that identity on the target repository (typically `write` for installed apps). The standard authorization gate applies; the platform does not default schedule or manual actors to `role: none`. +### Fullsend-originated entity discovery + +This is the future path adopted by +[ADR 0098](../../../ADRs/0098-entity-first-harness-evaluation.md); it is not yet +implemented by `fullsend dispatch` or `fullsend poll`, and entity-first +execution MUST NOT be enabled until the versioned normalized-entity contract +defines CEL-eligible and prompt-eligible fields. That contract is a platform +allowlist; a harness may request fewer fields but cannot expand it. + +`fullsend poll` may perform scheduled entity discovery without synthesizing a +`NormalizedEvent` or event actor. This path is authorized only when its caller +has trusted Fullsend-controlled invocation provenance: a verified, +non-user-assertable platform execution identity bound to the invocation and its +target, such as an attested workflow/job identity or installation credential. +A CLI flag, request header, or other caller-supplied claim is insufficient. A +caller that cannot establish that provenance MUST be denied. Wildcard +eligibility (`*` or `all`) requires the same explicit platform-level +justification as any other wildcard allowlist in this contract. + +Authorization of the poll origin does not authorize entity content. For each +candidate harness, before evaluating its CEL predicate, Fullsend MUST enumerate +a platform-defined closed superset of action-indicating elements for the entity +kind. These are actor-originated entity-history elements whose content or state +could be treated as a request for a stage to run; the superset includes issue or +change-proposal bodies, comments, reviews, and label applications wherever the +entity kind supports them. A harness cannot exclude a supported category from +classification. If enumeration fails, is unavailable, or does not cover the +entity kind, evaluation of that entity MUST be denied; only successful +enumeration may return an empty set for a state-only predicate. + +Fullsend MUST resolve the current forge permission level for every enumerated +element's actor and remove the element unless that permission meets the +candidate harness's observation or mutation threshold. Historical or cached +actor relationships are insufficient. A harness CEL predicate MAY further +restrict selection using retained elements and their current actor-permission +fields, but cannot weaken the platform gate or be relied upon to identify an +element for later authorization. + +Fullsend MUST omit or minimize other unneeded untrusted fields where the +normalized entity contract permits, while retaining the state, content, and +actor provenance required by the harness. Further trust or injection filtering +MAY run after CEL routing and before the harness pre-script. Retained content +remains untrusted throughout. Any resulting agent run uses the harness's +configured identity and permissions. + ## Excluded fields The following fields are **not** authorization evidence and must not be @@ -165,35 +222,56 @@ used for dispatch gating: | `actor.is_entity_author` | Being the author of an issue or PR does not grant repository permissions. This field supports routing decisions in CEL triggers, not authorization. | **Principle:** relationship and contribution-history fields are not -evidence of current authority. Authorization must be derived from the -forge's permission model at event time, not from cached or inferred -relationships. +evidence of current authority. Event-backed authorization must be derived from +the forge's permission model at event time, not from cached or inferred +relationships. Fullsend-originated entity discovery must instead be authorized +from trusted Fullsend-controlled invocation provenance for enumeration and +evaluation. Action-indicating elements are separately gated by current actor +permission; historical actors and relationship fields must not serve as +authorization evidence. ## Enforcement point -Authorization is enforced as a **platform-level gate** inside -`fullsend dispatch`, after `NormalizedEvent` normalization and **before** -CEL trigger evaluation. +Authorization is enforced as a **platform-level gate** before CEL trigger +evaluation. Event-backed dispatch uses the normalized event actor; +Fullsend-originated entity discovery uses trusted invocation provenance to +authorize enumeration and evaluation, then independently filters +action-indicating elements by current actor permission before CEL. ``` Forge event --> NormalizedEvent (adapter) - --> Authorization gate (this contract) <-- enforced here + --> Event-actor authorization gate <-- enforced here --> CEL trigger evaluation (harness routing) --> Execution + +Fullsend poll invocation + --> Trusted-origin authorization gate <-- enforced here + --> Entity enumeration and resolution + --> For each candidate harness: + --> Enumerate action-indicating elements + --> Harness-stage actor gate <-- enforced here + --> CEL trigger evaluation (event is null) + --> Further trust/injection filtering + --> Execution ``` ### CEL triggers: routing only -Harness `trigger` expressions express **routing**, not permission policy. +Harness `trigger` expressions express **routing and may tighten input +selection**, not platform permission policy. A CEL expression may **tighten** dispatch conditions (e.g., require a specific label, restrict to non-fork PRs, filter by bot identity) but -may **never weaken** the platform authorization gate. An event that fails -authorization never reaches CEL evaluation. - -This separation is enforced architecturally: `IsAuthorized()` runs -before `MatchHarnesses()` in the dispatch core. There is no mechanism -for a CEL expression to override or relax an authorization denial. +may **never weaken** the platform authorization gate. On the event-backed path, +an event that fails authorization never reaches CEL evaluation. + +This separation is enforced architecturally: on the event-backed path, +`IsAuthorized()` runs before `MatchHarnesses()` in the dispatch core. On the +entity-discovery path, the trusted-origin gate runs before enumeration and CEL +evaluation. For each candidate harness, the platform removes unauthorized +action-indicating elements using that harness's stage threshold before its CEL +predicate runs, and `event` remains null. Neither path lets a CEL expression +override or relax an authorization denial. ### Per-repo configurability diff --git a/docs/normative/normalized-event/v1/README.md b/docs/normative/normalized-event/v1/README.md index 2205844733..aebbcaf4ac 100644 --- a/docs/normative/normalized-event/v1/README.md +++ b/docs/normative/normalized-event/v1/README.md @@ -10,11 +10,16 @@ scope covers GitHub, GitLab, and Jira** (see [Scope](#scope-v1)). - **Schema:** [`normalized-event.schema.json`](normalized-event.schema.json) - **CEL context:** harness `trigger` expressions receive a single root variable - `event` bound to a `NormalizedEvent` object. + `event` bound to a `NormalizedEvent` object. This describes the currently + shipped event-backed path. [ADR 0098](../../../ADRs/0098-entity-first-harness-evaluation.md) + adopts a future entity-first context with required `entity` and nullable + `event`; its field-level contract remains follow-up versioned work. - **Authorization:** `fullsend dispatch` enforces the [Authorization Contract v1](../../authorization/v1/) as a platform-level gate after normalization and **before** CEL evaluation. Harness `trigger` - expressions express routing only, not permission policy. The historical + expressions express routing only, not permission policy. This authorization + statement applies to the event-backed path; Authorization Contract v1 also + defines the trusted-origin gate for future entity discovery. The historical decision is recorded in [ADR 0054](../../../ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md). diff --git a/docs/problems/agent-architecture.md b/docs/problems/agent-architecture.md index 58e44bec62..bc0cb40639 100644 --- a/docs/problems/agent-architecture.md +++ b/docs/problems/agent-architecture.md @@ -145,11 +145,15 @@ The repository's existing infrastructure provides all the coordination needed: - **Branch protection rules** define what's required before merge (status checks, approvals) - **CODEOWNERS** defines who (human or bot account) must approve changes to which paths - **Required status checks** ensure all review sub-agents have posted their findings -- **GitHub events** (PR opened, comment posted, status check completed) trigger agent actions +- **Forge events and scheduled entity discovery** identify entities whose + harness predicates should be evaluated. Each predicate can inspect the + resolved entity and an optional prompting event + ([ADR 0098](../ADRs/0098-entity-first-harness-evaluation.md)). No agent orchestrates other agents. Each agent independently observes the state of the PR and acts according to its role: -1. A PR is opened → review sub-agents are triggered (by webhook/GitHub event) +1. A PR is opened → its entity is resolved and review sub-agent predicates are + evaluated with the GitHub event as context 2. Each review sub-agent independently evaluates the PR and posts its findings (as status checks or structured comments) 3. If a review sub-agent requests changes → the code agent sees the comment and responds (treating it as untrusted input, but recognizing blocking authority if the reviewer has approval rights) 4. The merge decision is a **deterministic function of state**: all required status checks pass, all required CODEOWNERS approvals present, no blocking reviews outstanding diff --git a/docs/problems/security-threat-model.md b/docs/problems/security-threat-model.md index 37fa488742..fababb7c1d 100644 --- a/docs/problems/security-threat-model.md +++ b/docs/problems/security-threat-model.md @@ -116,7 +116,11 @@ Agents amplify authority. If a compromised account can trigger agent actions, th ### Defense considerations -- **Agent actions are attributable** — every agent action traces back to the triggering event and the human who initiated it +- **Agent actions are attributable** — every agent action traces back to the + triggering event and human, or, for state-only scheduled discovery, to the + verified platform invocation and human-reviewed policy and harness revisions + that authorized it (see + [ADR 0098](../ADRs/0098-entity-first-harness-evaluation.md)) - **No self-approval** — an agent that implements a change cannot also approve it - **Rate limiting / anomaly detection** — unusual patterns of agent activity (sudden burst of cross-repo changes, changes to security-sensitive paths) trigger alerts - **CODEOWNERS for agent config** — changes to agent rules, permissions, and configuration always require human approval