Skip to content

feat(kernel): make external-egress disclosure hook origin-symmetric (#207) - #257

Merged
George-RD merged 1 commit into
mainfrom
dev-207
Aug 21, 2026
Merged

feat(kernel): make external-egress disclosure hook origin-symmetric (#207)#257
George-RD merged 1 commit into
mainfrom
dev-207

Conversation

@George-RD

@George-RD George-RD commented Aug 21, 2026

Copy link
Copy Markdown
Owner

What & why

Final disclosure ticket (#207, spec #204 story 7). The external-egress disclosure hook must fire identically regardless of dispatch origin — worker-requested, kernel-origin, or the proactive headless lane with no principal present. There must be no second, ungated path for autonomous outbound content.

Root cause: the shared mediation entrypoint hardcoded ActionOrigin::Shell in its single gate() call, so the kernel-origin capability of the mediation body was untested and no blessed kernel-origin dispatch entry existed. The disclosure hook itself was already origin-independent (it triggers purely on the catalog egress_class after Allow).

Changes

  • Parameterize mediate_and_dispatch_action_with_attribution_and_token on ActionOrigin, threaded into the single gate() call. All existing wrappers keep passing Shell (no behavior change).
  • Add the blessed kernel-origin entry mediate_and_dispatch_action_kernel_origin — dormant #[allow(dead_code)], mirroring the repo's escalation::resolve_grant_for_thread idiom. It's the one path a future proactive/autonomous producer routes rated outbound content through, instead of hand-rolling a connector call. The disclosure hook is unconditional on egress_class after Allow, so it runs identically for any origin.
  • Production-entering tests (drive the real mediation entrypoints): a rated email.send with an uncovered disclosure policy blocks and raises OwnerQuestion for kernel-origin, worker-origin, and the proactive headless lane — all identical. Plus an invariant proving the one hand-rolled kernel-origin path (notify_owner_with_digest) carries no gated egress because owner.notify stays unrated and owner-facing (criterion 2: no ungated path).
  • CLAIM-43 in docs/threat-claims.md.
  • Repair a pre-existing concurrent-merge break: Bind provenance label to typed-identity origin (generalize ProducedBy) #224 added BriefcaseSection.origin; Wire email.send into the generalized disclosure hook end-to-end #206's messaging test predated it and no longer compiled.

Design notes

  • No RatedActionRefused guard on notify_owner_with_digest (proposed by the auto-generated plan): that path only dispatches owner.notify (owner-facing, unrated), which the disclosure hook does not govern. A bespoke refusal would be a second divergent behavior (refuse ≠ spec-mandated OwnerQuestion) with an unreachable trigger. Expressed as an invariant test instead.
  • No OpenSpec change: retired for new work per DIRECTION.md; the guarantee is recorded via CLAIM-43.
  • The standing-rule timer redispatch path funnels through the same worker-origin shared entrypoint, so its hook is the one proven for ActionOrigin::Shell; the claim/tests scope themselves to what's directly exercised (headless lane) and do not overclaim a dedicated timer test.

Verification

./scripts/check.sh green (fmt, clippy -D warnings, full cargo test --workspace, file-size, claims, omp-ceremony, openspec validate --all --strict).

Closes #207

Implementation-notes summary

  1. Deviations: 4 (dropped CodeRabbit notify-refusal guard as an unreachable second path; skipped OpenSpec change per DIRECTION.md; used real email.send over a synthetic action because the disclosure core resolves egress from the canonical catalog; dormant #[allow(dead_code)] kernel entry rather than #[cfg(test)]).
  2. Most likely to be revisited: the kernel-origin entry's dormant #[allow(dead_code)] — drops when a real kernel-origin rated-dispatch producer lands.
  3. Edge cases: concurrent-merge break (BriefcaseSection.origin) repaired; route_escalation delivers the owner Telegram message before appending the owner.question audit, so tests mock it.
  4. No irreversible/scope-changing decisions; no blocking questions.
  5. Read first: crates/openspine-kernel/src/api/actions.rs (origin param + enforce_rated_disclosure) and disclosure/core.rs (trusted_egress_class uses the canonical catalog).

Summary by cubic

Makes external-egress disclosure enforcement origin-symmetric so rated outbound content is mediated the same for worker-origin, kernel-origin, and headless dispatch. Previously the shared mediation entrypoint hardcoded ActionOrigin::Shell, leaving kernel-origin without a blessed path and risking an ungated route.

  • Parameterizes mediate_and_dispatch_action_with_attribution_and_token with ActionOrigin; existing wrappers still pass Shell (no behavior change).
  • Adds mediate_and_dispatch_action_kernel_origin as the single blessed kernel-origin dispatch entry for future proactive/autonomous producers; currently dormant.
  • Adds production-entering tests that drive real entrypoints: an uncovered rated email.send blocks and raises OwnerQuestion identically for worker, kernel, and headless; asserts owner.notify remains unrated and owner-facing (no gated egress).
  • Records CLAIM-43 and repairs a concurrent-merge break by setting BriefcaseSection.origin in a test. No OpenSpec change; no migration actions.

Written for commit 45ba784. Summary will update on new commits.

Review in cubic

…207)

Extend the disclosure hook so it fires identically for kernel-origin and
proactive/timer/headless dispatches as for worker-requested ones. Per spec
#204 story 7 there must be no second, ungated path for autonomous outbound
content.

What changed:
- Parameterize the shared mediation entrypoint
  (`mediate_and_dispatch_action_with_attribution_and_token`) on `ActionOrigin`
  and thread it into the single `gate()` call, replacing the hardcoded
  `ActionOrigin::Shell`. Every existing wrapper keeps passing `Shell`.
- Add the blessed kernel-origin dispatch entry
  `mediate_and_dispatch_action_kernel_origin` (dormant `#[allow(dead_code)]`,
  mirroring `escalation::resolve_grant_for_thread`): the single path any future
  proactive/autonomous producer routes rated outbound content through, so no one
  hand-rolls another connector call. The disclosure hook is unconditional on the
  action's catalog `egress_class` after Allow, so it runs identically regardless
  of origin.
- Production-entering tests (drive the real mediation entrypoints): a rated
  `email.send` with an uncovered disclosure policy blocks and raises
  `OwnerQuestion` for kernel-origin, worker-origin, and the proactive headless
  lane — all identical. Plus an invariant proving the one hand-rolled
  kernel-origin path (`notify_owner_with_digest`) carries no gated egress
  because `owner.notify` stays unrated and owner-facing.
- Register CLAIM-43 in docs/threat-claims.md.
- Repair a pre-existing concurrent-merge break: #224 added
  `BriefcaseSection.origin`; #206's messaging test predated it and no longer
  compiled.

No new openspec change (retired for new work per DIRECTION.md); the guarantee is
recorded via CLAIM-43.

Closes #207

Implementation-notes summary:
1. Deviations: 4 (dropped CodeRabbit notify-refusal guard as an unreachable second path; skipped OpenSpec change per DIRECTION.md; used real email.send over a synthetic action because the disclosure core resolves egress from the canonical catalog; dormant #[allow(dead_code)] kernel entry rather than #[cfg(test)]).
2. Most likely to be revisited: the kernel-origin entry's dormant #[allow(dead_code)] — drops when a real kernel-origin rated-dispatch producer lands.
3. Edge cases: concurrent-merge break (BriefcaseSection.origin) repaired; route_escalation delivers the owner Telegram message before appending the owner.question audit, so tests mock it.
4. No irreversible/scope-changing decisions; no blocking questions.
5. Read first: crates/openspine-kernel/src/api/actions.rs (origin param + enforce_rated_disclosure) and disclosure/core.rs (trusted_egress_class uses the canonical catalog).
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@George-RD, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c55c18a-6731-4ab0-a872-b46a230933c4

📥 Commits

Reviewing files that changed from the base of the PR and between 0d4d741 and 45ba784.

📒 Files selected for processing (5)
  • crates/openspine-kernel/src/api/actions.rs
  • crates/openspine-kernel/src/api/disclosure_origin_tests.rs
  • crates/openspine-kernel/src/api/mod.rs
  • crates/openspine-kernel/src/disclosure/disclosure_messaging_tests.rs
  • docs/threat-claims.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@George-RD
George-RD merged commit bedf694 into main Aug 21, 2026
3 checks passed
@George-RD
George-RD deleted the dev-207 branch August 21, 2026 07:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend the disclosure hook to kernel-origin and proactive dispatch paths

1 participant