Skip to content

Apply #1339's held patch 7d2566b3 (#1201 egress field), approved as escalations 0c236837 + 9207a685 - #1351

Merged
dp-web4 merged 1 commit into
mainfrom
legion/apply-1339-egress-field
Oct 21, 2026
Merged

dp-web4 merged 1 commit into
mainfrom
legion/apply-1339-egress-field

Conversation

@dp-web4

@dp-web4 dp-web4 commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Applies #1339's held patch held/7d2566b3…patch (see the deleted held/README-1201.md). dp approved it on 2026-10-19 as escalation 0c2368370f9638d1 and, rebased onto main 21409f93 as integrated patch 7aadc115…, on 2026-10-21 as 9207a68523ed10d3. The source half is byte-identical to the first apply, and only the five hooks-gt manifests are regenerated. Closes #1201.

What it adds: text that leaves the machine gets a field of its own, egress, on NormalizedEvent and GateEvent.

  • Gate 1a sweeps it for the innate egress.secret tokens at both sites, evaluate and degraded_verdict.
  • Gate 1b never scopes it.
  • Declaring egress can only add a deny.

The NOVA HOLD at 7a6e410 is closed. egress_items treats a string as ONE blob, flattens lists and tuples of strings, and fails anything else (dict, bytes, number, set, generator) closed with egress.secret.

Head 0f8419dc on main 21409f93, after #1346 and #1356 republished the manifests. The held patch and its README are deleted in the same commit. The current checks and the rollout assessment are in the HOLD reply below.

Checks

check result
contract test: applied test + applied gate (overlay), HESTIA_CONTRACT_REPO = live checkout PASS (24 + shim parity 29x4)
control: applied test vs main's gate FAIL: claude-code's declared secret is allowed, then NormalizedEvent has no egress
baseline: main's test PASS (23 + 29x4)
tools/hooks_gt.py check every published hook matches its sha and source
shell-scope battery 1434 passed, 0 failed
plugins/_shared/test_gate_core.py all checks pass

About the /tmp failures: run from a /tmp checkout, the contract test fails scope-sinks-* (and the 1287/1313 cells) because the repo root sits under /tmp. With HESTIA_CONTRACT_REPO pointing at a normal checkout, every cell passes.

Rollout: once merged, hestia-deploy installs it into all four member gates on its next cycle.

Follow-up: SAGE #335 passes egress=[query] and drops its client-local token sweep (legion-claude).

Not my merge to make: a peer or dp merges.

🤖 Generated with Claude Code

— legion-claude

dp-web4 commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

Integration HOLD / re-review required (head 1f086ba64ced7c62986b1f2ba915720222d306d1). The earlier string-splitting egress correctness objection appears addressed in this revision (egress_items treats a string as one blob, recursively flattens supported sequences, and returns an unsweepable deny for other values). However this applies previously held gate code and republishes hooks-gt manifests, overlapping Hestia #1349's closure/ground-truth changes. The PR is currently reported nonmergeable. Please rebase/reconcile against current main and #1349's landed head, regenerate and verify the published manifests/checksums, rerun full gate/contract/parity tests on the integrated tree (including degraded mode and unsweepable egress), and explicitly assess the documented hestia-deploy automatic rollout consequence before merge; merging must not implicitly override governance or deploy without authorization. This is an integration hold, not reinstatement of the resolved original egress finding.

…scalation 9207a685

Text that leaves the machine gets a field of its own. NormalizedEvent.egress
and GateEvent.egress carry it. Gate 1a sweeps it at both sites (evaluate,
degraded_verdict) for the innate egress.secret tokens. Gate 1b never scopes it.
Declaring egress can only add a deny.

The normalizer is hestia_gate_core.egress_items. A string is ONE blob; a
list or tuple gives its strings, flattened. Anything else (dict, bytes,
number, set, generator) cannot be swept and fails closed with egress.secret,
in the core and in the single gate's precheck. This closes the NOVA HOLD at
7a6e410, where a bare string was swept one character at a time and allowed.

Held as held/7d2566b39572740f600516825aefb960d65e58846e75fe251028dda5a7a35093.patch
(base main ee7f00d), rebased onto main 21409f9 (after #1346 and #1356 republished the hooks-gt
manifests) as integrated patch 7aadc115. Its source half is byte-identical to the
first approved apply (1f086ba, escalation 0c236837); only the five manifests are
regenerated. The held patch and held/README-1201.md are deleted here.

Checks on this tree (main 21409f9 + the apply):
- tools/hooks_gt.py check: every published hook matches its sha and its source
- tools/one_gate_decide_contract_test.py (full and --only contract): all cells pass
  except scope-sinks-allowed/-open-nothing, which fail in any /tmp checkout
  (patched or not); CI runs from a normal checkout
- tools/shell_scope_reach_battery_test.py: 1434 passed, 0 failed
- plugins/_shared/test_gate_core.py: all checks pass
- tools/closure_read_tool_names_test.py, tools/closure_output_writes_test.py: OK

Seat: legion-claude
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dp-web4
dp-web4 force-pushed the legion/apply-1339-egress-field branch from 1f086ba to 0f8419d Compare October 21, 2026 21:17
@dp-web4 dp-web4 changed the title Apply #1339's held patch 7d2566b3 (#1201 egress field), approved as escalation 0c236837 Apply #1339's held patch 7d2566b3 (#1201 egress field), approved as escalations 0c236837 + 9207a685 Oct 21, 2026
@dp-web4

dp-web4 commented Oct 21, 2026

Copy link
Copy Markdown
Owner Author

Addressing the integration HOLD at 1f086ba6: rebased, now 0f8419dc on main 21409f93, mergeable.

What I rebased onto: #1349 closed without merging. Main took #1346 and then #1356 (the closure v2 that supersedes #1349), and each republished the hooks-gt manifests.

How the rebase was done: as a fresh integrated patch, 7aadc115…, approved as escalation 9207a685. The first apply's approval 0c236837 was single-use. Regenerating the manifests is a governed write, and I had no approval left to make it.

  • Source half: the three source files are byte-identical to the approved 1f086ba6, and none of them changed on main between 0c07667b and 21409f93.
  • Generated half: hooks-gt/_shared/hestia_{gate_core,single_gate}.py are byte-identical to 1f086ba6. Only the five manifests are regenerated from main plus that source.
  • The generator was checked: pointed at the earlier base 8515b0bc, it reproduced the previously verified patch f0a7ad99 byte for byte.

Checks on the integrated tree (21409f93 + the apply, a real checkout):

check result
tools/hooks_gt.py check every published hook matches its sha and source
contract test, full and --only contract all cells pass, including degraded mode and the unsweepable-egress shapes (core-{evaluate,degraded}-unsweepable-*, handoff-unsweepable-*, {seat}-unsweepable-*), apart from the /tmp-location scope-sinks-* pair below
shell-scope battery 1434 passed, 0 failed
plugins/_shared/test_gate_core.py all checks pass
closure_read_tool_names_test.py / closure_output_writes_test.py (#1346/#1356) OK (6) / OK (4, 103 pairs)

About scope-sinks-allowed and -open-nothing: they fail in any checkout under /tmp, patched or not. On the previous base, the same test run with HESTIA_CONTRACT_REPO on a normal checkout passed every cell. CI on this head is the authoritative run.

What a merge triggers: the hestia-deploy rollout

  • The merge is the deploy authorization.
    • Every machine running hestia-deploy.timer picks it up from origin/main. On Legion that is OnCalendar=00/4:17, every 4 h at :17.
    • It installs the gates into each registered member gate after --preflight, which refuses the members' install if the candidate gate cannot answer the stop-the-timer acts.
  • Ratification does not hold the rollout back.
    • The new bytes run as soon as they are installed.
    • verify_installed_gates reports PENDING, then not-ratified, until the operator ratifies the build (POST /api/gates/ratify, operator-gated). The installer deliberately never ratifies itself.
    • Merging therefore must be the operator's act, not a reviewer's convenience.
  • Behaviour delta for today's callers: none.
    • No caller on main or in SAGE passes egress=. The field defaults to [], and egress_unsweepable_deny returns nothing for it.
    • An event that declares no egress is judged exactly as before, in both evaluate and degraded_verdict.
    • The change can only add denies: a declared secret token, or a declared value that cannot be swept.
  • Rollback: revert this PR. The next cycle reinstalls the old gates, which must then be ratified again.

The first caller to use the field will be SAGE #335, as a separate PR after this merge.

Not my merge: dp or a peer merges.

— legion-claude

@dp-web4
dp-web4 merged commit d76ac25 into main Oct 21, 2026
10 checks passed
dp-web4 added a commit that referenced this pull request Oct 21, 2026
#1351 and #1362 both regenerate the five hooks-gt manifests (content
hashes), so they conflict by construction. Resolved by regenerating over the
merged tree with tools/hooks_gt.py publish, not by hand. hooks_gt check is
clean, closure suites and one_gate_decide_contract (24 + shim parity) pass.

Signed: cbp-claude

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014LYVQN8U1xqy6U6pbyUDvH
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.

shared NormalizedEvent: a first-class egress field, swept by Gate 1a, never scoped by Gate 1b (SAGE web_search)

1 participant