Elnor Repo Reader

S6 E0 Adj Card and Egress Fix Reviews.md

Memory Rebuild Docs/Flattening/Reviews/Stage 6 Reviews/Stage 6 E0 Red Teaming/S6 E0 Adj Card and Egress Fix Reviews.md

Short text page 8d3f0be4550b. Generated 2026-06-09T01:23:58.539Z from commit dbaa25962edc11ab30e8d4ca1715f9ae5bf77331. Worktree: clean.

Open readable HTML page · Open raw txt · Open path URL

ELNOR REPO READER TEXT MIRROR
Original path: Memory Rebuild Docs/Flattening/Reviews/Stage 6 Reviews/Stage 6 E0 Red Teaming/S6 E0 Adj Card and Egress Fix Reviews.md
Source repo: /Users/OpenClaw1/Elnor/Elnor Specs
Git branch: main
Git commit: dbaa25962edc11ab30e8d4ca1715f9ae5bf77331
Generated: 2026-06-09T01:23:58.539Z

---

# ChatGPT

I treated the uploaded review prompt as controlling and reviewed **only §22 “Egress enforcement binding”** of `DOC80 S6 E0 RT Adj Card R2.md`, as requested. I also followed the repo access discipline: the repo README says to use `REPO_FILE_MANIFEST.md` when browsing/search is incomplete and to fetch exact paths with spaces/capitalization preserved; the manifest confirms it is the exact-path navigation aid for the repo.

I did **not** write to GitHub. Your standing Elnor instruction says to ask before writing to the repo, so I’m outputting the save-ready review here.

# ChatGPT S6 E0 §22 Egress Review

**Target artifact:** `Memory Rebuild Docs/Flattening/Reviews/Stage 6 Reviews/Stage 6 E0 Red Teaming/DOC80 S6 E0 RT Adj Card R2.md`
**Reviewed section only:** §22 “Egress enforcement binding” / UR-49 through UR-53
**Reviewer:** ChatGPT
**Verdict:** **NOT READY AS-IS; FOLD AFTER FIXES.** §22 is directionally correct and valuable, but it needs several hard fixes before locking into the revision: the attestation is under-keyed, `local_file_export` exposes a PropA citation conflict, KDA is over-assigned as a universal egress gate, fixture coverage misses several destination classes, and the “same ledger” convergence claim needs a precise shared-ledger shape.

------

## One-line verdict

**§22 should fold into the E0 revision only after the Critical and Substantive fixes below are applied.** No `ARCHITECT_STOP`; this is a strong additive section, but not yet A-grade as written.

------

# Critical findings

## C1 — CONFIRMED / Critical: UR-49 identifies a real enforcement gap

§22 is right that the **egress model exists** but is not mechanically bound to every send-site. PropA already defines the governing posture: EC’s `PolicyDecisionEngine` runs “at every outbound / injection / sync boundary,” followed by DOC24/DOC11 dispatch with receipts, manifests, and reason traces. PropA also defines a concrete sharing destination enum with `same_machine_local_runtime`, `local_file_export`, `local_network_peer`, `firm_server`, `remote_peer`, `cloud_api`, `email_outbound`, and `agent_messaging`.

But the fetched supporting specs I could verify do **not** show a build-failing mechanical invariant that every outbound action envelope carries a destination-matched policy decision. EC §8 export defines only `full_raw_backup`, `firm_tenant_only`, and `sanitized_anonymized` export scopes, plus receipt retention, not a destination-bound egress attestation. §22’s diagnosis — “egress binding is asserted, not enforced” — is therefore correct.

**Required fix:** keep UR-49. Do not downgrade it. This is not a phantom issue.

------

## C2 — BUG / Critical: §22’s outbound-class statement is directionally right, but PropA has a `local_file_export` citation conflict that §22 must surface

§22 says PropA’s matrix principle means all destination classes except `same_machine_local_runtime` are outbound. PropA’s `SharingRuleSchema` includes `local_file_export` as a destination class. But PropA’s normative matrix principle #2 explicitly lists `local_network_peer`, `firm_server`, `remote_peer`, `cloud_api`, `email_outbound`, and `agent_messaging` as outbound boundaries — **omitting `local_file_export`**. Immediately after, principle #3 says `same_machine_local_runtime` is the only non-egress local execution destination class.

That means the intended logic is almost certainly “`local_file_export` is egress,” but the cited PropA text is internally inconsistent.

**Required patch to §22:**

```md
PropA cleanup note:
`SharingRuleSchema.destination` includes `local_file_export`. PropA §2.6 principle #2 omits
`local_file_export` from the explicit outbound-boundary list, while principle #3 says
`same_machine_local_runtime` is the only non-egress local execution class. E0 treats
`local_file_export` as an outbound/egress class for policy-binding purposes and queues a
PropA patch to add it to principle #2 or reword principle #2 as “all destinations except
same_machine_local_runtime.”
```

**Required cross-doc obligation:**

```text
OPA: PropA §2.6 outbound-boundary principle must explicitly include local_file_export
or state that all SharingRuleSchema destinations except same_machine_local_runtime are outbound.
```

This is not pedantry. `local_file_export` is the path most likely to exfiltrate privileged material as an artifact.

------

## C3 — BUG / Critical: `E0EgressAttestation` is under-keyed and cannot prove it gates the actual action

The proposed attestation carries destination class, policy decision ID, decision destination, outcome, receipt ref, disclosure re-evaluation flag, optional redaction map, and schema version. That is not enough to bind a specific send/export/render action to the policy decision.

Missing fields:

```text
action_envelope_ref
action_kind
payload_manifest_ref or payload_hash
actual_terminal_destination_ref
declared_destination_ref
policy_generation_id
policy_evaluator_hash
source_ledger_ref
memory_coordination_trace_ref
interaction_mode
created_at
enforced_by
```

Without these, the system can prove that *some* policy decision existed, but not that it matched the actual action, payload, policy generation, recipient/channel/provider, or terminal adapter destination. The bug is especially severe for connector-sent email, DOC12 channel projection, agent messaging, and cloud API calls.

**Replace the contract with this shape:**

```ts
// schema_owner: DOC80. Enforcement bodies live in EC/DOC24/DOC11/DOC12/DOC84/DOC85 as applicable.
export type E0OutboundDestinationClass =
  | "same_machine_local_runtime"
  | "local_file_export"
  | "local_network_peer"
  | "firm_server"
  | "remote_peer"
  | "cloud_api"
  | "email_outbound"
  | "agent_messaging";

export type E0EgressDecisionOutcome =
  | "allow"
  | "warn"
  | "scope"
  | "redact"
  | "strip"
  | "block";

export type E0EgressActionKind =
  | "render_to_model"
  | "raw_artifact_export"
  | "connector_send_email"
  | "agent_message"
  | "channel_projection"
  | "cloud_api_call"
  | "sync_or_network_transfer";

export type E0InteractionMode =
  | "interactive"
  | "background_non_interactive"
  | "scheduled"
  | "agent_initiated";

export type E0EgressAttestation = {
  schema_version: 1;

  attestation_id: string;
  created_at: string; // RFC3339 UTC

  action_envelope_ref: string;
  action_kind: E0EgressActionKind;

  outbound_destination_class: E0OutboundDestinationClass;

  /**
   * The declared destination from the action envelope and the terminal destination
   * resolved after connector / adapter / channel dispatch.
   */
  declared_destination_ref: string;
  actual_terminal_destination_ref: string;

  /**
   * Optional only for same_machine_local_runtime where destination-policy lookup is not required.
   * For every other destination class, this MUST resolve.
   */
  policy_lookup_required: boolean;
  policy_decision_id?: string;
  policy_decision_destination?: E0OutboundDestinationClass;
  policy_generation_id?: string;
  policy_evaluator_hash?: string;

  decision_outcome: E0EgressDecisionOutcome;

  /**
   * Required when action is background_non_interactive and the matrix decision is warn:
   * the resolved outcome MUST be block.
   */
  interaction_mode: E0InteractionMode;

  receipt_ref: string;

  disclosure_reevaluated_at_boundary: true;

  payload_manifest_ref: string;
  payload_hash: string;

  source_ledger_ref?: string;
  memory_coordination_trace_ref: string;

  redaction_map_ref?: string;
  strip_receipt_ref?: string;
  scope_receipt_ref?: string;
  consent_or_override_ref?: string;

  enforced_by:
    | "EC"
    | "DOC24"
    | "DOC11"
    | "DOC12"
    | "DOC84"
    | "DOC4"
    | "connector_runtime";
};
```

**Add invariant:**

```text
For every outbound action, the attestation must bind: action envelope, payload hash/manifest,
actual terminal destination, policy generation, decision outcome, receipt, and trace. A decision
for the declared destination is insufficient if connector or adapter resolution changes the
terminal destination.
```

------

## C4 — BUG / Critical: same-machine “no lookup” conflicts with required `policy_decision_id`

§22 says `same_machine_local_runtime` is the only class that may carry `decision_outcome: "allow"` without a destination-policy lookup. But the proposed `E0EgressAttestation` requires `policy_decision_id` unconditionally.

That is internally contradictory.

**Required fix:** add `policy_lookup_required: boolean`, make `policy_decision_id` optional only when `outbound_destination_class === "same_machine_local_runtime"`, and require a sentinel receipt explaining the no-lookup class.

```ts
policy_lookup_required: outbound_destination_class extends "same_machine_local_runtime" ? false : true;
policy_decision_id?: string; // required iff policy_lookup_required === true
```

Since TypeScript does not express that conditional cleanly in a single object, use a discriminated union:

```ts
type SameMachineEgressAttestation = E0EgressAttestationBase & {
  outbound_destination_class: "same_machine_local_runtime";
  policy_lookup_required: false;
  policy_decision_id?: never;
  policy_decision_destination?: never;
  decision_outcome: "allow" | "scope" | "block";
};

type PolicyBoundEgressAttestation = E0EgressAttestationBase & {
  outbound_destination_class:
    | "local_file_export"
    | "local_network_peer"
    | "firm_server"
    | "remote_peer"
    | "cloud_api"
    | "email_outbound"
    | "agent_messaging";
  policy_lookup_required: true;
  policy_decision_id: string;
  policy_decision_destination: E0OutboundDestinationClass;
};

type E0EgressAttestation =
  | SameMachineEgressAttestation
  | PolicyBoundEgressAttestation;
```

Also clarify that “no destination-policy lookup” does **not** waive injection, collection, final-prompt, or memory-application policy gates. PropA separately says the `PolicyDecisionEngine` runs at outbound, injection, and sync boundaries.

------

## C5 — BUG / Critical: `warn` is missing from `E0EgressDecisionOutcome`

PropA’s sharing action enum includes `warn`, and its normative principles require `warn && interaction_mode = background_non_interactive` to coerce to `block`. §22’s outcome enum omits `warn`, replacing it with `scope`.

That loses a canonical PropA state and makes it impossible to lint the background-warn-to-block rule.

**Required fix:**

```ts
export type E0EgressDecisionOutcome =
  | "allow"
  | "warn"
  | "scope"
  | "redact"
  | "strip"
  | "block";
```

**Required lint:**

```text
egress.warn_background_not_coerced_to_block
```

**Required fixture:**

```text
egress.warn_background_non_interactive_coerces_to_block
```

------

# Substantive findings

## S1 — BUG / Substantive: KDA is over-assigned as the universal egress gate

UR-53 says KDA should require **every** egress path, not only card render, to route through `enforceKdaRenderPolicy`. That is overbroad. KDA can own render-policy enforcement for rendered knowledge delivery, but it should not be the universal gate for raw artifact export, connector-sent email, or DOC12 channel projection.

The correct split is:

```text
E0 owns: invariant, attestation contract, lints, fixtures.
EC/DOC24 owns: action-envelope policy gate.
PropA owns: destination matrix semantics.
KDA/DOC84 owns: rendered-content policy enforcement.
DOC11/OpenClaw owns: final prompt / runtime as-sent truth.
DOC12 owns: channel projection dispatch policy.
Connector/DOC16/DOC4 as applicable: terminal send/export adapter calls.
```

**Patch UR-53:**

```md
- **KDA / DOC84:** every rendered-content egress path routes through render-policy enforcement.
  KDA is not the universal egress gate for raw artifact export, connector email, or DOC12 channel
  projection; those paths are governed by the EC/DOC24 egress action gate and PropA matrix.
```

This preserves DOC80 scope discipline and avoids turning KDA into a shadow policy router.

------

## S2 — GAP / Substantive: fixtures do not cover every egress destination class

The five fixtures cover email, agent messaging, cloud API render, raw artifact export, and channel projection. They do **not** directly cover `local_file_export`, `local_network_peer`, `firm_server`, `remote_peer`, or `same_machine_local_runtime`. Yet PropA’s destination enum includes all of those.

**Add fixtures:**

```text
egress.same_machine_local_runtime_no_destination_lookup_allowed
  — same-machine local runtime action ⇒ attestation with policy_lookup_required=false; still emits receipt.

egress.local_file_export_privileged_requires_policy_decision
  — privileged artifact → local_file_export ⇒ destination policy decision required; outcome block/scope/redact per matrix.

egress.local_network_peer_privileged_blocks_or_scopes
  — privileged source → local_network_peer ⇒ block or scope with receipt.

egress.firm_server_client_confidential_warn_or_scope
  — client_confidential → firm_server ⇒ warns/scopes per PropA matrix and emits receipt.

egress.remote_peer_privileged_blocks
  — privileged source → remote_peer ⇒ block.

egress.warn_background_non_interactive_coerces_to_block
  — any `warn` result in background_non_interactive mode ⇒ block.

egress.connector_email_terminal_destination_mismatch_fails
  — declared email_outbound recipient/domain differs from terminal connector destination ⇒ fail closed.

egress.cloud_api_tool_call_requires_egress_attestation
  — non-render cloud API call carrying source-derived content ⇒ attestation required.
```

The current five are good seed fixtures, but they are not complete enough for “every outbound boundary.”

------

## S3 — BUG / Substantive: “sanitized” in fixture outcome does not match the attestation outcome enum

Fixture `egress.raw_artifact_export_to_non_local_blocks_or_sanitizes` says outcome can be `block` or `sanitized`. But `E0EgressDecisionOutcome` has `allow | scope | redact | strip | block`, not `sanitized`. EC’s export scope has `sanitized_anonymized`, but that is an export-scope value, not an egress decision outcome.

**Patch fixture name/outcome:**

```text
egress.raw_artifact_export_to_non_local_blocks_or_redacts
  — privileged artifact → non-local export ⇒ block or redact/strip with redaction_map_ref / strip_receipt_ref.
```

Or add an explicit mapping:

```text
EC ExportScope.sanitized_anonymized maps to E0EgressDecisionOutcome.redact plus redaction_map_ref.
```

------

## S4 — GAP / Substantive: destination class is too coarse for email, agent messaging, and channel projection

`email_outbound`, `agent_messaging`, and `cloud_api` are class-level values. They do not encode actual recipient, tenant, workspace, agent, domain, provider, or connector endpoint. The policy decision must match the terminal destination, not merely the class.

**Add required fields:**

```ts
declared_destination_ref: string;
actual_terminal_destination_ref: string;
recipient_principal_ref?: string;
recipient_domain_ref?: string;
connector_ref?: string;
channel_ref?: string;
agent_ref?: string;
tenant_ref?: string;
```

**Add lint:**

```text
egress.declared_destination_differs_from_terminal_adapter_destination
```

This is essential for email and channel projection. “Email outbound” to your own mailbox is not the same risk as “email outbound” to opposing counsel or a public address.

------

## S5 — GAP / Substantive: `policy_decision_id` must be replayable against a generation/hash

PropA says EC compiles the matrix into the effective policy artifact. EC §8 treats policy decision receipts as immutable for forensic replay and keeps compiled effective policy generations. The attestation must therefore include the policy generation and evaluator hash.

**Add fields:**

```ts
policy_generation_id: string;
policy_evaluator_hash: string;
compiled_policy_ref: string;
```

**Add lints:**

```text
egress.policy_generation_missing
egress.policy_evaluator_hash_missing
egress.policy_decision_not_replayable
```

Without these, the attestation is not litigation-grade.

------

## S6 — BETTER_IDEA / Substantive: the delivered/truncated/dropped ledger can be one primitive, but only if it has two views

UR-53 claims the per-turn delivered/truncated/dropped source ledger needed by egress binding is the same primitive learning needs. Directionally yes. DOC24 already has the principle that utility attribution joins packet manifest, final prompt manifest, and reconciliation events, and emits signals only for cards present in the final prompt manifest with a reconciled outcome.

But egress and learning need different projections:

```text
Learning view:
- what reached final prompt
- what was trimmed/suppressed
- what got a reconciled outcome
- utility attribution eligibility

Egress view:
- what crossed an outbound boundary
- what terminal destination received it
- what policy decision governed it
- whether redaction/strip/scope occurred
```

**Fix the convergence note:**

```md
Convergence:
Build one canonical per-turn source ledger under `MemoryCoordinationTrace`, but give it two required
views: `final_prompt_delivery_view` and `egress_boundary_view`. The same source/span rows may back both,
but the egress view must add terminal destination, policy_decision_id, decision outcome, redaction/strip
receipts, and egress attestation ref. If those fields are absent, the learning ledger is insufficient for
egress and a second egress ledger will be required.
```

**Suggested contract:**

```ts
export type SourceDeliveryDisposition =
  | "delivered"
  | "truncated"
  | "dropped"
  | "suppressed"
  | "redacted"
  | "stripped"
  | "blocked";

export type E0PerSourceTurnLedgerRow = {
  schema_version: 1;
  row_id: string;
  trace_ref: string;
  turn_id: string;
  source_ref: string;
  context_product_instance_ref?: string;
  prompt_span_ref?: string;

  final_prompt_disposition?: SourceDeliveryDisposition;
  final_prompt_truth_ref?: string;

  egress_disposition?: SourceDeliveryDisposition;
  egress_attestation_ref?: string;
  outbound_destination_class?: E0OutboundDestinationClass;
  actual_terminal_destination_ref?: string;

  reason_codes: string[];
  created_at: string;
};
```

------

## S7 — BUG / Substantive: §22 needs an explicit “actual destination after adapter resolution” invariant

A user action may declare “send to agent” or “export file,” but the terminal destination is resolved later by a connector, adapter, OpenClaw tool, or channel dispatch. §22 currently says policy decision destination must equal the action’s actual destination, which is correct, but the contract does not enforce where actual destination comes from.

**Add invariant:**

```text
The policy decision must bind the terminal destination after connector/adapter/channel resolution,
not only the destination declared in the user-facing action envelope. If terminal destination cannot
be resolved before dispatch, egress fails closed.
```

**Add lint:**

```text
egress.terminal_destination_unresolved_before_dispatch
```

------

## S8 — GAP / Substantive: UR-53 cross-doc owners are mostly right, but missing DOC20/DOC21/DOC22, DOC23, connector/DOC16/DOC4 paths

UR-53 correctly assigns EC/DOC24 gate step, DOC24 source ledger, PropA matrix binding, DOC11/OpenClaw final prompt capture, DOC12 channel projection, and DOC84/DOC85 ledger consumption.

But it is missing these owner paths:

```text
DOC20 — user-visible export/send/share surfaces, blocked/degraded receipts, why-blocked display.
DOC21/DOC22 — any new persistent UI components/pages/settings introduced by egress enforcement.
DOC23 — task modules that perform outbound send/export/sync actions.
DOC16 / connector specs — M365/email/calendar/Teams or other provider-level outbound actions.
DOC4 — native bridge tools that can perform send/share/export actions outside DOC24 render.
```

**Patch UR-53:**

```md
- **DOC20 / DOC21 / DOC22:** egress-blocked, egress-redacted, and egress-scoped receipts must be visible
  on any send/export/share surface; register new UI surfaces/components if created.
- **DOC23:** outbound task modules must call the EC/DOC24 egress gate before provider execution.
- **DOC16 / connector specs / DOC4:** provider/native outbound adapters must expose terminal destination
  before dispatch and must not bypass the egress attestation gate.
```

------

## S9 — CONFIRMED with caveat: DOC11/OpenClaw final-prompt-span extension is correct, but not sufficient

UR-53 says to extend existing DOC11/OpenClaw final-prompt-span rows rather than create a new row. That is correct **for final-prompt as-sent capture**. It should not become the only cross-doc row for all egress enforcement.

**Patch:**

```text
Extend existing FinalPromptSpan rows for final-prompt as-sent capture.
Add separate egress-gate obligations for DOC11/DOC4/native/connector outbound actions that can dispatch
outside final-prompt rendering.
```

This avoids mixing “what reached the model prompt” with “what crossed an outbound boundary.”

------

## S10 — BUG / Substantive: `same_machine_local_runtime` must not become a loophole for local agents with external side effects

PropA says `same_machine_local_runtime` is the only non-egress local execution destination class. That is fine for local model/runtime execution. It is **not** safe if a same-machine local agent then uses tools that send email, call a cloud API, post to a room, or export a file.

**Add invariant:**

```text
same_machine_local_runtime is no-lookup only for execution that remains within the same machine and
does not itself invoke an outbound tool. A local agent or local runtime action that invokes email,
cloud_api, channel projection, remote peer, network peer, firm server, or file export must create a
new egress attestation for that terminal outbound action.
```

**Add lint:**

```text
egress.same_machine_runtime_invoked_outbound_tool_without_attestation
```

------

# Minor findings

## M1 — MINOR BUG: lint attribution should preserve canonical/proposed status

§22 marks all four lints as proposed but says two are already named in Flatten plan §17.4. Search confirms `policy.bare_render_action` and `policy.export_stamp_without_destination` occur in the flatten plan.

**Patch:**

```text
Canonical from Flatten §17.4:
- policy.bare_render_action
- policy.export_stamp_without_destination

Proposed Stage 9:
- egress.outbound_action_without_destination_policy_decision
- egress.destination_mismatch_between_action_and_policy
```

Do not label canonical inherited lint names as merely proposed.

------

## M2 — MINOR GAP: add `egress.attestation_missing` as the umbrella lint

The current proposed lint `egress.outbound_action_without_destination_policy_decision` is useful but too narrow. It checks policy decision, not the entire attestation.

**Add:**

```text
egress.outbound_action_without_egress_attestation
```

Then `egress.outbound_action_without_destination_policy_decision` can remain the specific missing-decision variant.

------

## M3 — MINOR GAP: clarify receipt expectations for `block`

§22 says `receipt_ref` exists and that scope/redact/strip/block emit receipts. PropA explicitly says redact/strip generate receipts, and EC §8 retains policy decision receipts for forensic replay. It should be explicit that `block` also emits a receipt.

**Patch:**

```text
All non-allow outcomes — warn, scope, redact, strip, block — emit receipts. Block is not silent.
```

------

## M4 — MINOR SUGGESTION: rename `disclosure_reevaluated_at_boundary` to distinguish attestation from decision

`disclosure_reevaluated_at_boundary: true` is useful, but it is not a reference. For audit, the attestation should carry:

```ts
boundary_disclosure_reevaluation_ref: string;
```

If there is no separate artifact, keep the boolean and add `reevaluation_reason_codes`.

------

# Considered and declined

## D1 — Do not reject §22 because PropA already has fail-closed defaults

PropA’s fail-closed rule is real: provisional/unclassified/deferred/quarantined states fail closed for all outbound destinations except same-machine local runtime. That does not remove the UR-49 gap. Fail-closed defaults only help if the outbound action actually asks the policy system before dispatch.

## D2 — Do not move the whole section to PropA or EC

§22 is correctly located as a DOC80/E0 invariant/attestation/lint/fixture addition, provided enforcement bodies remain cross-doc. E0 should not implement EC/DOC24/DOC11/KDA behavior, but it should bind the family-wide invariant.

## D3 — Do not classify this as Minor

This is privilege egress. For attorney-client and work-product material, a missing send-site gate is not a minor lint-quality issue.

------

# Required patch set

Apply these before folding §22 into the revision.

## Patch 1 — replace INV-E0-EGRESS-1

```md
> **INV-E0-EGRESS-1 (DOC80-owned).** No outbound action may cross an egress
> boundary without an attached `E0EgressAttestation` binding the action envelope,
> payload manifest/hash, actual terminal destination after adapter/connector/channel
> resolution, policy generation, policy decision, decision outcome, receipt, and
> MemoryCoordinationTrace. For all destination classes except `same_machine_local_runtime`,
> the attestation MUST carry a resolved `policy_decision_id` whose destination equals
> the actual terminal destination class. Missing policy decision, unresolved terminal
> destination, stale policy generation, destination mismatch, or permissive-by-omission
> fails closed. `same_machine_local_runtime` is no-lookup only for execution that remains
> on the same machine and invokes no outbound tool; any terminal outbound tool call creates
> a new egress action and must be attested.
```

## Patch 2 — replace `E0EgressAttestation`

Use the expanded discriminated shape in C3/C4 above.

## Patch 3 — add lints

```text
Canonical:
- policy.bare_render_action
- policy.export_stamp_without_destination

Proposed Stage 9:
- egress.outbound_action_without_egress_attestation
- egress.outbound_action_without_destination_policy_decision
- egress.destination_mismatch_between_action_and_policy
- egress.terminal_destination_unresolved_before_dispatch
- egress.declared_destination_differs_from_terminal_adapter_destination
- egress.policy_generation_missing
- egress.policy_evaluator_hash_missing
- egress.policy_decision_not_replayable
- egress.redaction_without_redaction_map
- egress.strip_without_receipt
- egress.warn_background_not_coerced_to_block
- egress.same_machine_no_lookup_used_for_external_destination
- egress.same_machine_runtime_invoked_outbound_tool_without_attestation
- egress.agent_messaging_without_recipient_scope
- egress.channel_projection_without_policy_gate
- proof.rendered_prompt_export_missing_final_prompt_truth
```

## Patch 4 — expand fixtures

```text
egress.privileged_to_email_outbound_blocks
egress.privileged_to_agent_messaging_blocks_or_scopes
egress.sensitive_to_cloud_api_redacts
egress.cloud_api_tool_call_requires_egress_attestation
egress.raw_artifact_export_to_non_local_blocks_or_redacts
egress.local_file_export_privileged_requires_policy_decision
egress.local_network_peer_privileged_blocks_or_scopes
egress.firm_server_client_confidential_warn_or_scope
egress.remote_peer_privileged_blocks
egress.channel_projection_applies_policy
egress.same_machine_local_runtime_no_destination_lookup_allowed
egress.same_machine_runtime_invoked_outbound_tool_requires_new_attestation
egress.warn_background_non_interactive_coerces_to_block
egress.connector_email_terminal_destination_mismatch_fails
```

## Patch 5 — repair UR-53 owner obligations

```md
- **EC / DOC24 §21.1:** add the missing policy-decision gate step to SharedActionHandlerLayer:
  validate → resolve terminal destination → destination policy decision → egress attestation → execute.
- **DOC24:** emit the per-turn source ledger with final-prompt and egress-boundary views.
- **KDA / DOC84:** rendered-content egress paths route through render-policy enforcement; KDA is not
  the universal gate for raw export, connector email, or DOC12 projection.
- **PropA:** bind §2 outbound matrix mechanically; patch `local_file_export` omission in §2.6 principle #2.
- **DOC11 / OpenClaw:** extend final-prompt-span rows for as-sent prompt capture.
- **DOC11 / DOC4 / connector runtimes:** any native/provider outbound dispatch must expose terminal
  destination and call the egress attestation gate.
- **DOC12:** channel projection applies egress policy before dispatch.
- **DOC16 / connector specs:** email, calendar, Teams, browser, or provider-send actions must expose
  terminal destination before dispatch.
- **DOC23:** outbound task modules must call the EC/DOC24 egress gate before provider execution.
- **DOC20 / DOC21 / DOC22:** blocked/scoped/redacted egress receipts must be visible on send/export/share
  surfaces; register new UI surfaces/components/settings if introduced.
- **DOC84 / DOC85:** consume the final-prompt/egress source ledger for learning attribution.
```

## Patch 6 — revise convergence note

```md
Convergence:
Build one canonical per-turn, per-source ledger under `MemoryCoordinationTrace`, but require two views:
`final_prompt_delivery_view` and `egress_boundary_view`. The final-prompt view supports learning
attribution; the egress view supports outbound policy proof. They share source/span rows, but the
egress view adds terminal destination, policy decision, decision outcome, receipt, and egress
attestation refs. If those fields are absent, the learning ledger is not sufficient for egress.
```

------

# Value-tier decision

**Value tier:** **Critical / high**, not merely Substantive.

Reason: this governs attorney-client, work-product, personal, and client-confidential material at every exit point. PropA’s privilege classifier includes `attorney_client_privileged`, `work_product`, `firm_gc_privileged`, `privilege_uncertain`, and `not_privileged`, with rules to favor caution and mark review required on mixed evidence. PropA’s baseline matrix blocks attorney-client material for cloud, firm, local-network, and email destinations, and blocks work product for cloud/email while warning for firm/local-network. A missing send-site policy gate can therefore directly violate the litigation/confidentiality model.

The risk is partially mitigated by PropA’s fail-closed default for unclassified/provisional/quarantined states, but only if the outbound path calls the policy engine before dispatch.

------

# Final answer

**§22 is directionally right and should be kept, but it is not ready to fold as-is.**

It needs these changes first:

```text
1. Fix PropA/local_file_export ambiguity.
2. Expand E0EgressAttestation to bind action envelope, payload, actual terminal destination, policy generation/hash, receipt, and trace.
3. Add same-machine no-lookup discriminator.
4. Add warn/background coercion support.
5. Stop assigning all egress paths to KDA; KDA owns rendered-content policy, EC/DOC24 owns the general egress gate.
6. Add missing destination-class fixtures.
7. Split the per-source ledger into final_prompt_delivery_view and egress_boundary_view.
8. Add missing cross-doc owners: DOC20/21/22, DOC23, DOC16/connectors, DOC4/native bridge.
```

After those fixes, I would mark §22 **READY_TO_FOLD** and classify it as a **Critical egress-safety hardening section** rather than a mere Substantive add-on.

## One-line verdict

**Non-egress card status: `RATIFY_WITH_MINOR_LOCK_FIXES`, not “change the architecture.”** The R2 card accounts for essentially all prior non-egress findings, including the ones from my reviews, the Claude/ChatGPT adjudication, and the later “harvest findings, cut over-scoped bodies” correction. But I found a small final lock patch set before I would call it fully safe to freeze.

I reviewed the current repo card, not just the attachment. The repo README says to use exact paths from the manifest when search/browsing is incomplete, and the manifest confirms exact path/capitalization discipline. I also treated the egress prompt as out-of-scope for this pass except where it clarifies that §22 was the separate prior review target.

------

# What the R2 card now captures correctly

## 1. It captured the non-egress Card 1 + card-review issues

R2 explicitly says it folded two card-red-team rounds, about 80 edits, and no item from Card 1 was reversed. It lists the major non-egress fold-ins: `ContextProductKind`, final-prompt truth, MFC fixes, erasure/restamp/restore shells, EC seam pins, ContextProduct registry table, ADQ normalization, cross-artifact sweep expansion, closed verify table, and post-patch regression gate.

That is the right direction. The external adjudication also said the key move was to keep real findings while cutting premature E0-owned schema bodies; R2 largely follows that.

## 2. `ContextProductKind` is accounted for

The card now treats ABC §9.2’s 17 values as canonical, replaces the bad 14-value draft enum, and calls SM-060’s 14 stale. It also adds an ABC §9.3-aligned `ContextProductRegistryEntry` and requires a 17-row registry seed table, with uncertain owners marked instead of guessed.

That addresses the biggest P0 non-egress issue.

## 3. Final-prompt truth is correctly scoped

R2 adds `FinalPromptTruthRef` as a NAMED-only E0 hook, assigns the runtime body to DOC11/OpenClaw, and binds the invariant that executed-prompt render and learning/utility credit require final-prompt survival. This is exactly the correct scope cut: not a full DOC11 body in E0.

## 4. The MFC union is mostly in the right shape

R2 replaces the optional-ref interface with a discriminated union and adds `render_target`, `effective_state_generation_id`, `withheld_reason_codes`, and the erasure/restamp/restore certificate shells. That captures the important proof-spine issues.

## 5. The prior “hardening” items are present

R2 covers the non-egress items I cared about:

- per-product `MemoryContextProductRequest` and canonical ABC §9.4 disposition enum;
- ReproducibilityKey with learned-weight and registry-version inputs;
- trace correlation fields;
- edge-level membership proof;
- proof retention classes;
- ReasonCode, DomainProfile, WarrantDegradationTrigger, embedding generation, EC seam, source revocation, SemanticProjection, health, quota, background strategy, schema/version conventions, lifecycle non-equivalence, bitemporal split, MME/MPG handoffs, §4/§18/§19/§20 restoration, named-lint preservation, preservation matrix, gate table, and deferral register.

## 6. Source-pack drift and post-patch review are now represented

The card’s cross-artifact sweep now includes SM-060, Owner Map, Import Graph, Retired Names, Source Registry, Stage 6 Index, prompt path, ADQ Queue, and OP-A-related housekeeping. It also adds a numbered post-patch regression gate.

That fixes the earlier weakness where the card had the right edits but lacked a strong closure gate.

------

# Remaining non-egress changes I would make before locking

These are **not** architecture reopeners. They are lock-quality fixes to prevent later drift or misimplementation.

## 1. BUG / high — `MemoryDestructionLedger` storage ownership currently conflicts with EC durable-writer doctrine

R2 says the destruction ledger is an append-only, hash-chained seam, but then says “bodies/storage = DOC84 + DOC85 + DOC11.” That is not safe wording. A destruction/restamp/restore ledger is durable audit state. EC must be the write/storage owner, or you risk creating a second durable audit writer.

**Patch:**

```md
MemoryDestructionLedger ownership correction:
E0 names the seam and invariants.
EC owns durable append-only storage / write path.
DOC11 owns runtime truth refs that may be cited by the ledger.
DOC84 owns delivery/audit surfaces.
DOC85 owns learning-eligibility effects.
No DOC84/DOC85/DOC11 component may write the ledger except through EC.
```

Also change the deferral-register row from:

```text
DOC84 + DOC85 + DOC11
```

to:

```text
EC durable storage/write path; DOC84/DOC85/DOC11 consumers/surfaces/effects
```

This is the most important non-egress correction I found.

------

## 2. BUG / medium-high — `RestoreMFC.executor` is misleading and should be renamed

`RestoreMFC` has `executor: ProducerDocRef`, with a comment saying it is distinct from `issued_by: 'EC'`. The intent is understandable, but the field name `executor` is dangerous because EC is the actual durable executor. It creates avoidable ambiguity with the “EC sole durable writer” rule.

**Patch:**

```ts
interface RestoreMFC extends BaseMFC {
  outcome: 'issued';
  flow_kind: 'restore';
  ec_path: 'serialized_durable';
  restored_from: 'recycle_bin' | 'backup';
  prior_erasure_certificate_ref?: MemoryFlowCertificateId;
  mutation_envelope_ref: MemoryMutationEnvelopeRef;

  /**
   * The member/surface that requested or initiated the restore.
   * EC remains the durable executor and certificate issuer.
   */
  initiating_member_ref: ProducerDocRef;

  authorized_by: ActorRef;
  reason_codes: ReasonCodeId[];
}
```

Replace lint:

```text
restore.executor_equals_ec
```

with:

```text
restore.uses_executor_field_instead_of_initiating_member
restore.durable_restore_not_issued_by_ec
```

------

## 3. BUG / medium — `PromptShellExposure` uses a boolean where the whole review history says booleans are not proof

R2 adds `PromptShellLearningContract` and `PromptShellExposure` as NAMED-only stubs. That is good. But the stub says:

```ts
PromptShellExposure { shell_variant_id; exposed_in_final_prompt: boolean; context_product_instance_ids: string[] }
```



The boolean has the same flaw as the old `membership_eligibility_checked: boolean`: it asserts exposure instead of proving it. Since this is NAMED-only, we do not need a full body, but we should name the required proof ref.

**Patch:**

```md
PromptShellExposure NAMED-only correction:
The body is Stage 7 / DOC85-DOC11, but the E0 handoff must require:
- shell_variant_id
- final_prompt_truth_ref
- rendered_span_refs or equivalent DOC11 span refs
- trimmed/suppressed span exclusion
- context_product_instance_ids, where applicable

Do not use `exposed_in_final_prompt: boolean` as the proof field.
```

Add proposed lint:

```text
prompt_shell.exposure_boolean_without_final_prompt_truth_ref
```

------

## 4. BUG / medium — `RegistryEntryLifecycleState` should include `candidate`, or the card should explain why it does not

R2 correctly renames generic `LifecycleState` to `RegistryEntryLifecycleState` and restricts it to registry entries. But the value set is only:

```text
active | deprecated | retired
```

For controlled registries, especially ReasonCode, WarrantDegradationTrigger, ContextProduct, and RenderSafetyCheck vocabularies, a candidate state is useful and already appears in earlier registry mechanics. Omitting it encourages either undocumented pre-active rows or premature active entries.

**Patch:**

```ts
type RegistryEntryLifecycleState =
  | 'candidate'
  | 'active'
  | 'deprecated'
  | 'retired';
```

Add sentence:

```md
`candidate` entries are not runtime-emittable unless a registry-specific rule explicitly allows candidate/test fixtures.
```

Add lint:

```text
registry.candidate_entry_emitted_at_runtime
```

------

## 5. GAP / medium-high — `SourceRevocationCascade` needs an affected-set manifest, not only outcomes

The card now has the five-plane cascade and polarity correction, which is good. But the interface only names source, revocation event, required outcomes, EC receipts, and reason codes. It does not name how the affected support edges, memberships, delivery artifacts, learning signals, and Inspector surfaces are identified.

Without an affected-set manifest, the cascade can say “required outcomes happened” without proving the system found all affected objects.

**Patch:**

```ts
interface SourceRevocationCascade {
  source_ref: SourceRef;
  revocation_event_ref: MemoryMutationEnvelopeRef;

  /**
   * Manifest produced by the per-plane scan listing affected refs or
   * an auditable query/snapshot proving the affected set.
   */
  affected_set_manifest_ref: SourceRevocationAffectedSetManifestRef;

  required_outcomes: {
    doc82_support_edges: 'invalidated' | 'verify_required';
    doc87_memberships: 'restamped' | 'removed' | 'hidden';
    doc84_delivery_artifacts: 'invalidated';
    doc85_learning_signals: 'ineligible_for_future_utility';
    doc86_surfaces: 'safe_labeled' | 'suppressed';
  };

  ec_receipt_refs: ECReceiptRef[];
  reason_codes: ReasonCodeId[];
}
```

Add lint:

```text
revocation.cascade_missing_affected_set_manifest
```

This preserves E0 scope because it only names the manifest requirement; per-plane scan bodies stay downstream.

------

## 6. GAP / medium — `E0DurableRecord` should list all durable registry/audit objects it applies to, or say “including but not limited to”

R2 adds `E0DurableRecord` and lists objects that extend it. The list is good but not exhaustive. It omits or ambiguously omits `ReasonCodeNamespaceAllocation`, `SemanticProjectionAxisRegistration`, `SourceRevocationCascade`, and arguably `MemoryPlaneHealthCounter` if retained as a health record rather than a transient read model.

**Patch:**

```md
Records that extend or include E0DurableRecord include, at minimum:
ContextProductRegistryEntry, every MFC variant, MemoryDestructionLedger entry,
ExternalDependencyRecord, ProofArtifactRetentionRule, ReasonCode,
ReasonCodeNamespaceAllocation, WarrantDegradationTrigger, DomainProfile,
MemoryOperationQuota, SemanticProjectionContract, SemanticProjectionAxisRegistration,
SourceRevocationCascade, and any retained MemoryPlaneHealthReadModel record.
```

Or simply:

```md
The list is illustrative, not exhaustive; every durable registry, retained-proof,
or audit artifact must include `schema_version` and `created_at`.
```

------

## 7. MINOR BUG — the EC seam cross-artifact line should include EC §4 if §4 is pinned

The ECSeamContract pin block references EC §4 BackgroundJobOrchestrator for recycle-bin retention/expiry sweeps. But the cross-artifact sweep later says the Import Graph should confirm EC §1/§3/§7/§8 pins, omitting §4.

**Patch:**

```md
DOC80_Import_Graph.md — confirm EC §1/§3/§4/§7/§8 ECSeamContract pins appear as import edges;
no local redefinition of EC schemas.
```

Small, but it avoids another self-inflicted drift item.

------

## 8. MINOR — “No manual tracking required” should be reworded

The deferral register says “No manual tracking required.” That is directionally meant to say the card itself creates the trackers, but it conflicts rhetorically with OP-A discipline, which requires human maintenance at session close. The carryover explicitly says OP-A updates are non-negotiable at the close of every review/revision session.

**Patch:**

```md
No out-of-band memory is required once the §7 deferral register, §15.4 gate table,
§17.4 preservation matrix, OP-A rows, ADQ ledger, and cross-artifact sweep entries are updated.
```

------

# Things I would **not** change

I would not reopen any of these:

- full lifecycle/erasure engine — R2 correctly rejects it and keeps event certificates only;
- full `FinalPromptTruthRef` body — correctly deferred to DOC11/Stage 7;
- full lint/fixture/migration meta-schemas — correctly rejected/deferred;
- DOC81 policy-axis semantic values — correctly left to DOC81;
- DOC84 executed `RenderSafetyProof` body — correctly not owned by E0;
- ContextProduct naming — keep `ContextProduct`, do not rename to `ContextArtifact`; R2 correctly rejects the rename.

------

# Final answer

**Yes, the card accounts for the non-egress issues from the prior reviews.** It is now very close to lockable. I would not add another review round or reopen DOC80 architecture.

But before you lock it, I would apply this final non-egress lock patch:

```text
1. Make MemoryDestructionLedger EC-owned for durable storage/write path.
2. Rename RestoreMFC.executor to initiating_member_ref / requested_by.
3. Replace PromptShellExposure.exposed_in_final_prompt boolean with a required FinalPromptTruthRef handoff.
4. Add candidate to RegistryEntryLifecycleState.
5. Add affected_set_manifest_ref to SourceRevocationCascade.
6. Make E0DurableRecord coverage exhaustive or explicitly illustrative.
7. Add EC §4 to the Import Graph sweep.
8. Reword “No manual tracking required” to avoid conflicting with OP-A maintenance discipline.
```

After those, I would be comfortable saying the **non-egress part of the card is A-grade and ready to lock**, with §22/e­gress handled separately by the egress review patch set.

# Claude

New task — red-teaming the **new §22 (Egress enforcement)** added to the R2 card. This is the expansion of my earlier N8 finding into a full binding, so I'll hold it to the same bar and verify every citation against the actual specs (PropA, KDA, EC Core, DOC24). Let me re-establish repo access and pull §22 plus the cited sources.§22 content runs from ~68490 (UR-49) to the end. Let me read the whole block.Got the full §22 body. Let me read its header/lead-in (the "what exists today" audit), then verify every citation against the actual specs — starting with the two most load-bearing claims: DOC24 §21.1's `SharedActionHandlerLayer` step list and PropA's L94 / L783-784 / §2 matrix.§22's lead-in grounds the destination taxonomy in PropA §2 (the same 8 classes), which is good. Now I'll verify the load-bearing citations against the actual specs — PropA (L94, L783-784, §2 matrix L1080-1098, P1 classifier L1622) and DOC24 (§21.1 step list, §22 obligations, the `policy_decision_id` counts).PropA verified — the §2 taxonomy (L1081-1095) matches `E0OutboundDestinationClass` **verbatim**, the fail-closed rule is real (it's at L785, card said L783-784), and L94/L95 is indeed a *pipeline-flow* line ("EC PolicyDecisionEngine at every outbound/injection/sync boundary"), i.e., asserted-not-mechanically-enforced — which supports UR-49. Minor off-by-one cites (L94→L95, L783-784→L785, L1622→L1623). Now the decisive DOC24 checks: the `SharedActionHandlerLayer` step list, the §22 obligation IDs, and the `policy_decision_id` counts.DOC24 confirmed on the counts: `policy_decision_id` appears **exactly 11×**, `bare_render`/`render_without` appear **0×**, and both `OBL-D11-NEW-FINAL-PROMPT-SPAN-01` and `OBL-OPENCLAW-NEW-FINAL-PROMPT-SPAN-01` exist (5× each) — so UR-53's "extend the existing obligation" call is correct. Now the decisive one: the `SharedActionHandlerLayer` §21.1 step list — does it really lack a policy-decision gate step?**Verified exactly.** DOC24 §21.1 (char 176913): *"every action passes through `SharedActionHandlerLayer`, which: 1. Validates the action envelope (per §15). 2. Applies idempotency check. 3. Persists the receipt (post-execution). 4. Emits state change events."* — **no policy-decision gate step.** UR-49's central claim is precisely correct. Last verifications: KDA §3.2C, EC §8.1, and the "0×" claim across all four specs.Every citation in §22 checks out against the specs — I verified all of them. Here's the review.

# Claude — S6 E0 §22 (Egress Enforcement) Red-Team Review

**Artifact:** `…/Stage 6 E0 Red Teaming/DOC80 S6 E0 RT Adj Card R2.md` §22 (UR-49–UR-53), char 66900–74402. **Method:** every cited line read directly in PropA R6.3, DOC24 R3.1.1, KDA R3, EC Core Add A V3.3, Flatten §17.4. **Connector note:** my GitHub connector can write, but per your workflow I'm returning this inline (renders cleanly) and will commit on your word.

**Headline:** §22 is real, accurately grounded, and scope-disciplined — its central gap is verified true, not asserted. It is *not* ready to fold **as-is**: it under-tiers itself, it fails *open* on an unknown destination, and three of its cross-doc/contract details are wrong or thin. Fold it after the fixes below.

## CONFIRMED (verified — credit where due)

**[CONFIRMED] UR-49 gap is real and precisely characterized.** I verified all four legs:

- DOC24 **§21.1** (char 176913) `SharedActionHandlerLayer` is *literally* "1. Validates the action envelope (per §15). 2. Applies idempotency check. 3. Persists the receipt (post-execution). 4. Emits state change events." — **no policy-decision gate step.** Exactly as the card says.
- `policy_decision_id` = **11× in DOC24, 5× in KDA** (card's counts exact); `bare_render` / `render_without` / `outbound_action_without` = **0× in PropA, DOC24, KDA, and EC** (card's "0×" claim exact).
- PropA **L95** (card cites L94) is a *pipeline-flow* bullet — "EC PolicyDecisionEngine at every outbound / injection / sync boundary" — i.e., asserted, not a mechanically-enforced per-send-site gate.
- KDA **§3.2C** `enforceKdaRenderPolicy()` branches on `input.sharing_action` / `input.visibility_class` — it **enforces what it is handed and does not derive the decision from the privilege class**, exactly as UR-49 states. So a caller that passes a permissive/omitted `sharing_action` renders unchecked.

**[CONFIRMED] Taxonomy, fail-closed, lints, obligations all trace.** `E0OutboundDestinationClass` (8 values) == PropA **§2 `SharingRuleSchema.destination`** verbatim (L1083-1092). Fail-closed grounding is real (PropA **L785**: the four unclassified states fail closed for all outbound except same-machine). `policy.bare_render_action` + `policy.export_stamp_without_destination` **are** in Flatten **§17.4** — attribution correct. `OBL-D11-NEW-FINAL-PROMPT-SPAN-01` and `OBL-OPENCLAW-NEW-FINAL-PROMPT-SPAN-01` both exist (5× each) — so UR-53's "**extend** the existing obligation (not a new row)" call is correct. EC **§8.1** export classes (`full_raw_backup | firm_tenant_only | sanitized_anonymized`, encrypted) verified.

**[CONFIRMED] Scope discipline held.** DOC80 keeps to the invariant + attestation schema + lints + fixtures (`schema_owner: DOC80`); enforcement bodies (the gate step, KDA routing, PropA matrix binding, ledger emission) are routed to cross-doc OPA obligations and explicitly "discharged separately, NOT edited from this card." Correct.

## CRITICAL

**[ARCHITECT_STOP — re-tier] CR1 — this is Critical, not "Substantive (high)."** The verified gap is that **privileged / work-product content can egress to `cloud_api`, `email_outbound`, or `agent_messaging` with no failing check** (no gate step in §21.1; render enforces what it's handed; nothing fails on an absent / mismatched / permissive-by-omission decision). For a securities-litigation platform, uncontrolled privilege egress is a P0 confidentiality/waiver risk — case-dispositive, not merely substantive. Tier it **Critical**. (This is the litigation-fit answer to ASSESS #10.)

**[GAP — blocking] CR2 — INV-E0-EGRESS-1 fails \*open\* on an unrecognized destination.** The invariant is scoped to a destination "**in PropA's outbound set**." A send-site using a destination *outside* the eight (clipboard, printer, a third-party-synced folder, a novel MCP connector) is not covered → it egresses unchecked, which is exactly the failure mode §22 exists to kill. **Fix — default-deny on unknown destination:**

> *Amend INV-E0-EGRESS-1:* any outbound action whose destination is **not** `same_machine_local_runtime` and is **either** not a recognized PropA §2 destination **or** lacks a resolved, destination-matched, permitting decision → **fail closed.** Unrecognized destination ⇒ fail closed.

Add lint `egress.unrecognized_destination_not_failed_closed` and fixture `egress.unknown_destination_fails_closed`.

## SUBSTANTIVE

**[GAP] S1 — `E0EgressAttestation` is missing three fields.** As written it can't prove the thing it claims:

```ts
export type E0EgressAttestation = {
  outbound_destination_class: E0OutboundDestinationClass;
  policy_decision_id: string;
  policy_decision_destination: E0OutboundDestinationClass;   // MUST === outbound_destination_class
  policy_generation_id: string;            // ADD — boundary-freshness PROOF; disclosure_reevaluated_at_boundary:true is unverifiable without it (this was N8's whole point)
  payload_privilege_classes: PrivilegeClassPropA[];  // ADD — self-describing audit: which P1 classes left, per PropA L1623
  decision_outcome: E0EgressDecisionOutcome;
  receipt_ref: string;                     // required for ALL outbound incl. "allow" (see S6)
  memory_flow_certificate_id?: string;     // ADD — link to the RenderMFC/ExportMFC/DelegationMFC chain
  disclosure_reevaluated_at_boundary: true;
  redaction_map_ref?: string;              // required iff decision_outcome === "redact"
  schema_version: 1;
};
```

**[BUG] S2 — UR-53's KDA bullet is a category error.** "Require **every** egress path (not only card render) to route through `enforceKdaRenderPolicy`" is wrong: I verified `enforceKdaRenderPolicy` is render-only (it returns `render_allowed/blocked/stripped/redacted`). A raw-artifact export (EC §8), a connector email/agent send, or a DOC12 channel projection is **not** a render and cannot meaningfully pass through a render-policy function. **Fix the bullet:** *render* egress → `enforceKdaRenderPolicy`; **all** egress (render + non-render) → the §21.1 `SharedActionHandlerLayer` policy-decision gate. KDA is not the universal egress chokepoint; the §21.1 gate is.

**[SUGGESTION] S3 — unify with N8 and wire the MFC.** §22 says it "generalizes N8," but N8 put a `disclosure_scope_attestation_ref` on `ExportMFC`/`DelegationMFC`, and §22 introduces a parallel `E0EgressAttestation` — two disclosure records for the same event. State that N8's `disclosure_scope_attestation_ref` **resolves to an `E0EgressAttestation`** (one type), and add the egress `destination_class` (or the attestation ref) to the MFC itself — today the MFC has no destination field, so "render to `cloud_api`" vs "render to `same_machine`" are indistinguishable in the certificate.

**[BETTER_IDEA] S4 — the convergence is half-right; make it one record, two facets.** The delivered/truncated/dropped ledger and final-prompt source attribution share a per-source *record shape* but are **not one ledger** — they key off different events and diverge: a local same-machine inference is a final-prompt/learning event but **not** an external egress; an email/export is an egress event but **not** a final prompt. Building it only as the final-prompt-span ledger leaves non-render egress uncovered; only as an egress ledger leaves local-inference learning uncovered. **Fix:** build one MemoryCoordinationTrace per-source record with **two disposition facets** — `final_prompt_disposition` and `egress_disposition` — not one column serving both. (Keeps the card's "build once" instinct, fixes the scope.)

**[GAP] S5 — the five fixtures test only the happy path; the INV's teeth are untested.** All five are "privileged/sensitive source → external destination ⇒ block/redact." None exercises the actual invariant logic. Add:

- `egress.destination_mismatch_fails_closed` — `policy_decision_destination ≠ actual` ⇒ fail closed *(the core INV check — currently untested)*
- `egress.unclassified_source_fails_closed_except_same_machine` *(PropA L785 generalization)*
- `egress.same_machine_allows_without_lookup` *(proves the one carve-out works — and nothing else does)*
- `egress.privileged_to_firm_server_scopes` + `egress.local_export_sanitizes` *(EC §8.1 `firm_tenant_only` / `sanitized_anonymized`)*

**[GAP] S6 — `allow` egress to an external destination may leave no audit trail.** The contract comment says only "scope/redact/strip/block emit receipts," yet for litigation you need a record of **everything** that left the machine — including an `allow` of not-privileged content to `cloud_api`. The type already makes `receipt_ref` required; make the rule explicit ("receipt_ref required for all non-same-machine egress regardless of outcome") and add `egress.allow_to_external_without_receipt`.

## MINOR

**[SUGGESTION] M1 — don't redeclare PropA's enum.** `E0OutboundDestinationClass` is a verbatim copy of PropA §2's `SharingRuleSchema.destination` (PropA is senior). Import/align it, or add `egress.destination_class_enum_drift_from_propa` + an import-graph/supersession row — same "don't redeclare an owned vocabulary" discipline as UR-01.

**[SUGGESTION] M2 — two missing contract-integrity lints:** `egress.redact_outcome_without_redaction_map` (the contract requires `redaction_map_ref` when `decision_outcome==="redact"` — lint it) and `egress.enforced_outcome_without_receipt`.

**[BUG] M3 — trivial off-by-one citations** (fix so an agent lands on the right line): PropA **L94→L95**, **L783-784→L785**, **L1622→L1623**.

**[SUGGESTION] M4 — egress ∩ revocation.** One line: the destination policy decision must consult source-revocation state (UR-08/09 cascade) — a revoked source must not egress even if otherwise permitted. No schema change.

**[SUGGESTION] M5 — name the connector-egress gating owner.** UR-53 should assign who guarantees connector/MCP send-actions (Gmail/Slack) actually pass *through* the §21.1 gate (else it's bypassable). The deferred Claude Code probe verifies this empirically, but DOC24 §21.1 should be named as the interceptor.

## Considered-and-declined

- The prompt's "DOC4" appears to be a typo; the card's **DOC11** (OpenClaw runtime) / **DOC12** (channels) owners are correct — no change.
- Don't grow `E0EgressAttestation` into a policy-decision engine (PropA/EC own that) — scope discipline is right.
- No conflict with the 5-plane cascade, UR-29, or the §9 deliverable; §22 is additive and does not reopen §9. Verified.

## Verdict

**§22 NEEDS MINOR-TO-SUBSTANTIVE FIXES BEFORE FOLDING — not as-is.** Blocking: **CR1** (re-tier to Critical), **CR2** (fail-closed on unknown destination), **S1** (add `policy_generation_id` + `payload_privilege_classes` + `memory_flow_certificate_id`), **S2** (fix the KDA category error). Strongly recommended before fold: **S4** (one-record/two-facets) and **S5** (the negative/edge fixtures). With those in, §22 is an A-grade, litigation-correct egress binding — and the underlying gap it closes is the single most important one found in this whole E0 cycle.

------

Want me to (a) commit this to `…/Stage 6 E0 Red Teaming/Claude S6 E0 §22 Egress Review.md`, and/or (b) fold these fixes directly into a revised §22 block you can drop into the card? Say the word on either and I'll do it.

On it. The R2 card is ~23K chars larger than the original Adj Card 1 I reviewed, and only ~6–8K of that is §22 — so it looks like other things were folded in too. Let me read the R2 body (everything except §22), check whether my prior non-egress findings (review #1's 8 must-changes + the deeper-dive N1–N10) actually landed, and look for anything new. First, re-establish access and map the card.The R2 revision note is detailed and claims to fold in almost all of my prior findings. Now I need to verify the claims are actually executed correctly — and catch the ambiguous ones (e.g., it lists `render_target`, not the `render_mode` preview/executed split I flagged; it attributes "N9" to a new `restore` flow when my N9 was about retaining `executor`; and UR-37's narrowing isn't mentioned). Let me map every claim with a token grep, then read the key regions.R2 folded in nearly everything — `allowed_roles`/`header_fields_na` (UR-01), the plural `withheld_reason_codes` (N6), `erasure`/`policy_restamp`/`restore` MFC flows (N1/N10 + a new restore), `DebugModeContract` (F30), `PromptShellLearningContract` (F11), `embedding_generation_id` (F28), `ReproducibilityKey` registry inputs (N2), the destruction ledger, `IngestionCostBudget`/`WarrantConsequenceRegistry` (UR-34), §15.5 regression gate, and the trailing sign-off is stripped.

But four things need a closer look: **`render_mode` = 0** (they added `render_target` for destination, not the executed/preview split I flagged in finding 5); the revision note **mislabels "N9"** (my N9 was *retain `executor`*, not the new restore flow); **UR-37's narrowing** isn't mentioned; and I should confirm the **14→17 sweep** is actually mandated. Let me read the decided-forks tail and the MFC union.The four R2 forks are N3 (cascade), registry-owners-not-guessed, DebugModeContract scope, and verdict+§15.5 — so UR-37 and UR-38 are no longer forks (UR-38 accepted; UR-37 still in the card, need to check the narrowing). Now the MFC union region, to verify the flows, `render_target` vs preview-render, `executor` retention, and restore soundness.Several of my flagged concerns are actually resolved: UR-01 entry now has `allowed_roles` + `header_fields_na` + keyof typing + `audit_replay_class`; the **N4 sweep is mandated verbatim** ("sweep every remaining … '14' in §3.1/§6/§14/§15.3/§17.2 … do not touch the fold-in count"); and **finding 5 is resolved** — `render_target: 'executed_prompt'|'preview'|'export_render'` with `final_prompt_truth_ref?` "required iff `executed_prompt`" is exactly the preview/executed reconciliation (just named `render_target`).

But I notice the BaseMFC has **no `executor` field** — the three `executor` hits are all `packet_executor` in the registry entry. So my **N9 (retain MFC `executor`) looks dropped**, and the revision note mislabeled the new `restore` flow as "N9." Let me confirm by reading the rest of the MFC variants (restore/erasure/restamp soundness), then check UR-37's narrowing and the ReproducibilityKey field list.Even more is resolved than the revision note stated: `restamp.exceeds_original_ceiling` (N5) **is** present (I'd mis-searched the underscore form), `learning.credit_from_preview_render` (finding 5) is a lint, and **N9's `executor` is on `RestoreMFC`** (`// N9 — distinct from issued_by:'EC'`, with lint `restore.executor_equals_ec`) — so N9 landed, just narrowed to restore. The ErasureMFC/RestampMFC/RestoreMFC shells closely match what I specified.

Two new soundness questions occur to me on the restore/erasure shells, and I still need to confirm UR-37's narrowing and the ReproducibilityKey field list. Let me check those.Excellent — more confirmations: the **§10.11 citation is fixed** (V3 roll-up: "§10.11 = 6 planes-of-effect lints + Synthesis §4 = 1 (`learning_signal_survives`)"), the **monotonicity is scoped to supported assertions** (review-1 finding 2 ✓), the **N2 ReproducibilityKey** now has all three registry-version inputs verbatim, and **N7** is reconciled. The one thing still unconfirmed is whether **UR-37's DOC25 narrowing** (my finding 6) was applied. Let me read the actual UR-37 row.UR-37 is fully addressed with my narrowing verbatim (@31496: "v2 (Finding 6) narrows DOC25's grounding to parse / materialization / ingestion conditions, and routes source-revocation reason codes to DOC82").

So **every one of my prior non-egress findings is folded into R2** — I've now verified each one against the actual text. Let me do a final soundness pass on the genuinely new content (the destruction ledger and the new §12.1 invariants), since that's where any *new* issue would live, then give you the consolidated answer.I've now verified the R2 card line-by-line against both my prior reviews. Here's the result.

## Verdict: every non-egress finding is accounted for

R2 didn't just append §22 — it folded in **all** of review #1's eight must-changes *and* all of the deeper-dive N1–N10, and it closed the three source-review orphans I'd flagged (F11/F28/F30). I confirmed each against the actual text, not the changelog:

| Prior finding                      | Status in R2 (verified)                                      |
| ---------------------------------- | ------------------------------------------------------------ |
| **#1** UR-01 dropped fields        | ✓ entry now has `allowed_roles`, `header_fields_na`, keyof-typed `header_fields_required` (+`audit_replay_class`) |
| **#2** UR-08/09 monotonicity       | ✓ law scoped to *supported* assertions; contradicted net warrant may rise (V3 roll-up) |
| **#3** F11 prompt-shell hooks      | ✓ `PromptShellLearningContract` + `PromptShellExposure` NAMED-only |
| **#4** F30 debug mode              | ✓ `DebugModeContract` (decided fork #3: scoped to F30's text) |
| **#5** RenderMFC preview split     | ✓ `render_target:'executed_prompt'|'preview'|'export_render'`; `final_prompt_truth_ref?` required iff executed; lint `learning.credit_from_preview_render` |
| **#6** UR-37 narrowing             | ✓ DOC25 scoped to parse/materialization/ingestion; source-revocation codes → DOC82 |
| **#7** UR-34 tracking              | ✓ `IngestionCostBudget`→E3, `WarrantConsequenceRegistry`→E4 in §15.4 |
| **#8** citation + sign-off         | ✓ "§10.11 = 6 + Synthesis §4 = 1"; trailing chat sign-off stripped (ends clean at "End of E0…") |
| **N1** erasure flow                | ✓ `ErasureMFC` (kind, legal-hold clearance, irreversibility, reason codes) |
| **N2** ReproducibilityKey          | ✓ +`warrant_degradation_trigger_registry_version`, `reason_code_registry_version`, `domain_profile_registry_version` |
| **N3** DOC83 cascade               | ✓ decided fork #1: keep 5 planes, no 6th, no promotion invariant; clawback manual |
| **N4** 14→17 sweep                 | ✓ mandated across §3.1/§6/§14/§15.3/§17.2 + fixture tokens; fold-in count untouched (verbatim as I asked) |
| **N5** restamp ceiling             | ✓ lint `restamp.exceeds_original_ceiling` + `RestampMFC.ceiling_compliance_attested` |
| **N6** plural reason codes         | ✓ `withheld_reason_codes: ReasonCodeId[]`                    |
| **N7** learning row                | ✓ §12.1 reconciled "necessary, not sufficient"               |
| **N8** export/delegation boundary  | ✓ §12.1 invariant + `disclosure_scope_attestation_ref` + the §22 egress binding |
| **N9** retain executor             | ✓ `executor: ProducerDocRef` on `RestoreMFC` (narrowed — see D below) |
| **N10** restamp cert               | ✓ `RestampMFC`                                               |
| **F28 / Finding 9**                | ✓ `embedding_generation_id` + comparability invariant        |
| **Destruction-ledger BETTER_IDEA** | ✓ `MemoryDestructionLedger` (append-only, hash-chained, records erasure/restamp/restore; bodies DOC84/85/11) |

The destruction ledger is well-built — it records **restores too** (chain-of-custody), and the invariant requires ledger-append-before-effect. Scope discipline holds throughout (E0 owns the shells; bodies are cross-doc). So the non-egress side is clean.

## Anything else — four points, all on the *new* litigation proof layer

These are net-new (they're about content that didn't exist when I last reviewed), and two are litigation-relevant enough to fold before lock:

**[SUGGESTION · litigation] A — `ErasureMFC` gates legal hold only for `hard_destruction`; extend to `redaction`.** Today `legal_hold_clearance_ref` is "required iff `erasure_kind==='hard_destruction'`." But **redaction is partial destruction of held material — a classic spoliation vector**; redacting a document under legal hold needs clearance just as much as destroying it. Fix: require `legal_hold_clearance_ref` iff `erasure_kind ∈ {hard_destruction, redaction}`; add lint `erasure.redaction_under_legal_hold_without_clearance`. (Leave `soft_tombstone` ungated — it's recoverable.)

**[SUGGESTION · litigation] B — `RestoreMFC` has no re-evaluation invariant.** A restore re-introduces previously-erased material, but nothing stops it from resurrecting a **now-revoked source**, violating a **current legal hold**, or undoing a **`hard_destruction`** that was certified irreversible. It inherits `policy_generation_id`/`effective_state_generation_id` (so it's *stamped*) but there's no gate. Fix: add §12.1 invariant *"a restore re-evaluates current policy + revocation + legal-hold at restore time; a `hard_destruction` cannot be restored"*; lints `restore.reintroduces_revoked_or_held_material`, `restore.of_hard_destruction`. Also make `prior_erasure_certificate_ref` **required iff `restored_from==='recycle_bin'`** (a recycle-bin restore reverses a specific erasure — the ledger chain needs the link).

**[SUGGESTION · consistency, ties to §22] C — two parallel disclosure attestations now coexist.** Export/DelegationMFC carry `disclosure_scope_attestation_ref: DisclosureScopeAttestationRef` (N8), while §22 introduces `E0EgressAttestation`. When §22 folds, unify them — either one type, or state that `DisclosureScopeAttestationRef` *resolves to* an `E0EgressAttestation`. (This is finding **S3** from my §22 review; flagging it here because both now live in the card.)

**[Minor · optional] D — N9 `executor` is on `RestoreMFC` only.** That's the highest-value case ("who restored this deleted item"), so it's a fair narrowing. If you want full executor provenance (which member performed each render/export/durable_write, distinct from the EC certifier), move `executor: ProducerDocRef` up to `BaseMFC`. Optional.

## Bottom line

The non-egress body of R2 is **done** — nothing else there needs changing. The only outstanding work before lock is: (1) the **§22 fixes** from my prior review (CR1 re-tier, CR2 unknown-destination fail-closed, S1 attestation fields, S2 KDA category error, S4/S5), and (2) **A and B** above (litigation-grade, low-cost) plus **C** at §22-fold time. With those folded, I'd lock it.

Want me to (a) write a consolidated "R2 → lock" change-list (the §22 fixes + A/B/C/D) as a single doc, and/or (b) draft the paste-ready ErasureMFC/RestoreMFC edits for A and B? Say the word and I'll do it.