hyp session ignore's receipt reports the write it made, not a drop nothing verified - #522
hyp session ignore's receipt reports the write it made, not a drop nothing verified#522philcunliffe wants to merge 5 commits into
Conversation
…fied `hyp session ignore` printed "ignored - the gateway will drop this session". The gateway cannot know that: `control.js` adds an opaque token to a `Set` and answers `ignored: true` for whatever it was handed, and the drop happens later in the client adapter, keyed on the `session_id` that adapter stamps on the row (LLP 0066 R5). Nothing compares the two, so an id no exchange will ever carry - a Codex thread id, a finished session's id, a typo - printed the same line while the session kept being recorded. Per the maintainer direction on #460 (options 3 + 4), this is accept-and-document rather than a new gateway capability: making the route answer the grain question would put client-grain knowledge in a deliberately provider-agnostic control surface (LLP 0050). So the contract becomes the stated one and the receipt stops implying more. - LLP 0066 gains §receipt-is-membership and R14: `ignored: true` is set membership, the caller owns resolving the container first (R13), a read-back does not upgrade the claim, and any surface calling the route directly applies the CLI's response validation. LLP 0067 gains §cli-receipt. - The CLI receipt says "this id is in the gateway drop set" and prints what that proves, next to the success where a caller reading "ignored" as done sees it. `--json` carries `guarantee: "set_membership"` beside `status: "ok"`, since a bare `ok` is what the skills parse. The reader prints the same qualifier, from one shared constant, for the reason EPHEMERAL_NOTE is shared. - The three skills that POST the route directly gained the echo check the JS resolver already had (`session_id` compared byte-exactly against the id sent, `total` a real number), and each states the narrow contract in prose. Regression test constructs the exact false-confidence case rather than asserting a string: an id live traffic does not carry is registered successfully, the real Codex projector is then shown recording that traffic anyway, and the receipt printed over that outcome must not have promised a drop. Co-Authored-By: Claude <noreply@anthropic.com>
🤖 neutral: PR #520 and PR #522 both write the #460 contract, in different documentsThese two PRs were built in parallel this tick by workers blind to each other. Each is coherent alone; the overlap only bites if both merge. Flagging rather than resolving, because where a normative statement lives is your call. PR #520 ( PR #522 ( So after both merges the rule is stated normatively in two places. That is the drift LLP discipline exists to prevent, and a future reader has no signal which is authoritative. Options:
Also note: both PRs edit Neither PR is blocked on this; both remain individually reviewable. Reply here (or on the sibling PR) and neutral will apply your choice on a later tick. |
…o R14 too Two findings from the #460 review, both the reviewed change's own rule applied where it was not yet applied. The removal receipt printed "recording resumed", which is the same inference from the same `Set` answer read the other way: a token no exchange carried was suppressing nothing to resume, and `.hypignore` is an independent governor (R7) that can keep a session unrecorded whatever this verb removed. It now reports the removal it made. The three skill verifiers gate `total` with `isinstance(..., int)` while claiming to mirror `validateControlResponse`. `isinstance(True, int)` is True in Python, so a responder answering `total: true` passed the skill check and would have failed the CLI's `typeof total !== 'number'` - a gap in the very check LLP 0066 R14 makes a MUST for surfaces calling the route directly. bool is now excluded. Both are pinned by tests that fail without the fix, and LLP 0067 §cli-receipt records the removal half of the contract. Co-Authored-By: Claude <noreply@anthropic.com>
|
Verdict: approve, with two findings fixed in-branch ( Verification performed
Findings1. Low - Fixed: the headline now reads 2. Low - Fixed: Not findings, recorded so the next reader does not re-derive them
Known cross-PR overlap (already surfaced, no action taken here)PR #520 ( |
… was believing The CLI's removal receipt was corrected in round 1, but the skill that DELETEs the same route was left printing "Recording re-enabled for session <id>" off `--fail-with-body` alone, with the response body sent to /dev/null. That is the same mirrored overclaim, plus R14's last bullet (any surface calling the route directly applies the CLI's response validation) unapplied entirely rather than merely weakly: any 200 from whatever owns that port read as success. It now reads the reply and asserts `ignored` is a real boolean false, `total` a real number with bool excluded, and `session_id` echoed byte-exactly, then reports the removal in the CLI's words. Pinned by the shared skill loop plus a removal-specific test; LLP 0067 §cli-receipt records it. Co-Authored-By: Claude <noreply@anthropic.com>
|
Verdict: approve, with one finding fixed in-branch ( Round-1 fixes: verified, not taken on trustFix 1 (the unignore receipt) landed. Fix 2 (the bool hole) landed, and I exercised it. At Run through the whole extracted bash block, that is the receipt a hostile-or-confused responder got: At Fails closed, nothing printed. I swept eleven adversarial bodies (bool Finding1. Medium - Two separate breaches of the rule this PR mints:
The PR swept the three skills that Fixed ( Verified end to end by running the extracted bash block against a stubbed Fail-closed holds: the verifier sits inside Pinned two ways: the skill is added to the shared Verification performed this round
Not findings, recorded so a third reader does not re-derive them
Known cross-PR overlap (already surfaced, no action taken here)PR #520 ( |
🤖 neutral: triage rung — PR #522 can ship safely at
|
# Conflicts: # hypaware-core/plugins-workspace/ai-gateway/src/session_command.js
…the removed one The merge is textually one conflict (both sides added a constant after EPHEMERAL_NOTE in session_command.js) and one semantic one. Master's ai-gateway-session-responder-trust.test.js, landed with #520, pinned the headline this branch deletes - `ignored - the gateway will drop this session` - as the precondition for "a spoofed success reads as done". That string is exactly what LLP 0066 R14 forbids, so the assertion moves to the receipt this branch prints instead. The test's own claim is untouched: an impostor's confirmed `ignored` still has to carry the trust disclosure, and the drop set it names is still the impostor's. Co-Authored-By: Claude <noreply@anthropic.com>
neutral: triage rung - PR #522 can ship safely at
|
The defect
hyp session ignoreprinted:The gateway cannot know that.
control.jsadds an opaque token to aSetand answersignored: truefor whatever it was handed; the drop happens later, in the client adapter, keyed on thesession_idthat adapter resolves and stamps (LLP 0066 R5). Nothing in between compares the two. So an id no exchange will ever carry - a Codex thread id, a finished session's id, a typo - printed exactly that line while the session kept being recorded. As the triage on #460 recorded, aGETafter thePOSTdoes not help: it is the sameSet.hasover the same set.The direction taken
Per the maintainer's answer on #460 (options 3 + 4): accept-and-document, not a new gateway capability. Having the route resolve and echo the grain would put client-grain knowledge into a deliberately provider-agnostic control surface (LLP 0050), so the contract becomes the stated narrow one and the receipt stops implying more. Correctness stays where PR #458 put it: the caller resolves the container before it calls, and #435 will narrow that to one resolver.
What changed
Docs (design landed with the code). LLP 0066 gains
§receipt-is-membershipand R14:ignored: trueis set membership; a confirmed opt-out is reported as the write it is; a read-back does not upgrade the claim; machine-readable receipts are covered too; and any surface calling the route directly applies the CLI's response validation. LLP 0067 gains§cli-receipt, and the two places that quoted the removed string are updated.CLI (
session_command.js). The headline is nowignored - this id is in the gateway drop set, followed by aMEMBERSHIP_NOTEstating what that proves - printed next to the success, where a caller reading "ignored" as done will see it.--jsoncarriesguarantee: "set_membership"besidestatus: "ok", since a bareokis what the skills parse. The reader prints the same qualifier from the same constant, for the reasonEPHEMERAL_NOTEis shared: two statements of one contract drift.Route (
control.js). JSDoc states what the reply is a reply about, and why the route may not say more.Skills. The three shell paths that POST the route (
claude/hypaware-ignore,claude/hypaware-privacy,codex/hypaware-privacy) gained the echo check the JS resolver already had -session_idcompared byte-exactly against the id sent,ignoreda real boolean,totala real number - and each states the narrow contract in prose. Previously they printedopt-out confirmed for session <whatever the responder said>.Test evidence
test/plugins/ai-gateway-session-ignore-receipt.test.jsconstructs the exact false-confidence case rather than asserting a string in isolation: an id live traffic does not carry is registered successfully, the real Codex exchange projector is then shown recording that traffic anyway (the row is stamped with the container), and the receipt printed over that outcome is required not to have promised a drop.Before the fix (6/6 fail); the actual output captured by the failure is the defect itself:
After (6/6 pass):
Full suite:
3084 pass / 8 fail, against a3078 pass / 8 failbaseline on the sameorigin/master- the 8leave ...failures are pre-existing and untouched by this change.npm run typecheckclean;npm run smoke -- session_optout_capture_dropandcore_boot_noopboth ok.Scope note
Deliberately does not touch
§cli-response-check's open question about proving the responder is the gateway - that is #451, in flight separately. The echo check added to the skills is the existing CLI check being applied where it was missing, which the #460 review explicitly folded in.Fixes #460