Step 6: Proto types and frame codec #2
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Contract types in crates/proto (IDs, error registry, scopes, grants, plans, stream frames) and the frame codec with fuzz test and benchmark.
Step 6 in docs/PLAN.md. Done when: merged to dev
Starting step 6 on branch
proto.Base/head:
fac2890c93276e645d6ba439ff2291ba93f0eec4.Scope: crates/proto contract types, frame/log codecs, tests/benches, and generated OpenAPI/client outputs as required by the contract. No push; the orchestrator will fetch this branch.
Finding/decision: the skeleton already resolves bytes 1.12.1 and sha2 0.11.0 transitively; the proto crate will make them explicit for the zero-copy codec and parameter hashing. For RFC 8785 canonicalization, use serde_json_canonicalizer 0.3.2 rather than the older serde_jcs surface: its published docs describe RFC 8785 compatibility and note the reason for the fork. Primary references: https://docs.rs/bytes/latest/bytes/ ; https://docs.rs/sha2/latest/sha2/ ; https://docs.rs/crate/serde_json_canonicalizer/latest ; RFC: https://www.rfc-editor.org/rfc/rfc8785.html
Research worker start. Branch: proto. Base head (dev):
a9961a2198. Current head:fac2890c93. Scope: read-only inspection of docs/contracts.md and the crates/proto skeleton; no files will be edited and the issue will remain open.Starting contract-model lane on branch proto.
Base head:
fac2890c93Owned scope: crates/proto/src/models/* and crates/proto/tests/* only; no stream files, lib.rs, Cargo.toml, generated files, or docs.
Initial evidence: repo index is current at
fac2890c; the proto skeleton currently has only crates/proto/src/lib.rs with Health and ApiError/ApiErrorDetails, so the model modules will need later lib.rs/dependency/OpenAPI wiring by integration.Started stream-codec work on branch proto at base
fac2890c93.Scope: new stream sources, stream tests and benchmark only; lib/Cargo/OpenAPI integration will be reported for the orchestrator.
Research complete (read-only). Disjoint ownership: model lane owns crates/proto/src/models/{ids,errors,access,plans,canonical}.rs plus tests/models.rs; stream lane owns crates/proto/src/stream/{frame,state,payloads,records,codec}.rs plus tests/stream_codec.rs and benches/stream.rs; one integration owner alone edits lib.rs, Cargo manifests/lock, server OpenAPI registration, docs/openapi.json, api-client generated files, and scripts/verify bench. Findings: skeleton source is only src/lib.rs with Health and string-based ApiError; graph impact reaches server/src/lib.rs, server/benches/s0.rs, infraternal/src/main.rs. Contract gaps needing an explicit decision are the exhaustive error registry versus skeleton method_not_allowed, typed per-code details/retryability for see-above entries, unnamed OPEN/OPEN_OK/HELLO/grant schemas, and hash/signature encodings. Current head:
fac2890c93. Worktree has concurrent Cargo manifest/lock changes; no source edits by this worker. Gates: not run (read-only research). Issue remains open.Finding/decision: the codec now keeps channel 0 PING/PONG-only in the channel registry; HELLO/HELLO_OK are accepted only by the separate tunnel handshake state machine. Post-EOF rules are endpoint-aware: only a client may send RESIZE after its own EOF, and frames after a sender's CLOSE are rejected.
Frame::try_encodevalidates public headers before encoding. OpenAPI advertises the hex parameter hash as a string, bounded receive windows/dimensions, RFC3339 grant times, normalized tag pattern, andOPEN.paramsas an object. The generated OpenAPI and TypeScript schema were regenerated from Rust.Finished step 6 on branch
proto.Built:
Independent read-only review found and the implementation fixed wrong-peer acceptance, unbounded retired-id storage, permissive hop-specific RESIZE direction, generic OPEN parameter schema, zero-valued generated bounds, and opt-in HELLO/grant validation. The review's peer-EOF concern was checked against §3.4: only the side that sent EOF is restricted, so the opposite side may continue sending DATA.
Commits:
14a40ceproto: add contract types and stream codecsafec16aserver: publish proto schemas and gatesGate output (verbatim):
Head:
afec16a1c2a433fe971a9a638cb44158182bc073.Build job
proto(gpt-5.6-luna/max) finished. Author's report:Independent review started.
/mnt/hdd/infraternal/.worktrees/proto-reviewprotoafec16a1c2a433fe971a9a638cb44158182bc073dev:a9961a2198ca94bf9db1650efe5c56ee247229c1Independent review complete for
protoatafec16a1c2a433fe971a9a638cb44158182bc073againstdevata9961a2198ca94bf9db1650efe5c56ee247229c1.Verdict: reject. The branch has security and protocol-state violations that must be fixed before downstream auth and stream lanes build on these APIs.
Blocking findings
Filtered-to-unfiltered grant escalation.
crates/proto/src/permissions.rs:413-427treatsrequested.tags.iter().all(...)as coverage; an empty requested tag list is vacuously covered. A scratch test confirmed that role/token grantscontainers:read@tag=stagingcausevalidate_grant_escalation(..., Grant::new(ContainersRead))to returnOk(()), granting all hosts contrary to §2.3.Token minting bypasses the contract.
crates/proto/src/permissions.rs:460-471permitsHuman -> Humanprogrammatic minting even though human tokens may come only from interactive login, and succeeds without effectivetokens:manage; callers must remember to compose a second validator that the exported mint validator does not require.Connection-level frames are accepted on ordinary channels.
crates/proto/src/stream/frame.rs:886-923checks which frames may use channel 0 but not the inverse. Scratch state-machine probes returnedOk(Accepted)for bothPINGandHELLO_OKon channel 1, violating §§3.4 and 3.9.Peer traffic is accepted after peer EOF.
crates/proto/src/stream/frame.rs:650-667applies the post-EOF rule only tolocal_eof. A probe producedremote_eof=Ok(Accepted)followed bydata_after_remote_eof=Ok(Accepted), although the peer that sent EOF may then send only the listed control frames.Channel capacity handling breaks ID semantics.
crates/proto/src/stream/frame.rs:827-863checks capacity before duplicate/ordering checks and does not retire an ID refused withOPEN_ERR resource_exhausted. Probes producedduplicate_at_capacity=ResourceExhausted, close_code=1008rather than reused-ID 1002, and an ID refused at capacity was accepted later after another channel retired.Generated
OpenParamsis not a valid representation of legal OPEN payloads.docs/openapi.json:684usesoneOf, but the logs/events/stats branches are identical empty objects, so{}matches three branches and is invalid; exec and SSH parameter schemas also overlap.kindis not discriminated withparams, so the TypeScript union atpackages/api-client/src/schema.ts:266permits mismatched kind/params combinations. This defeats the normative OpenAPI schema.Persistent OPEN types admit forbidden values.
crates/proto/src/stream/controls.rs:82-122,157-226representsopen_keyasOption<String>and target asString; validation accepts an invalid exec target, empty/non-ULIDopen_key, persistent creation withoutopen_key, andresume_fromwithoutsession_id. Scratch tests confirmed these cases deserialize successfully, contrary to §§3.7 and 3.11.Error bodies are neither stable-shaped nor typed per code.
crates/proto/src/errors.rs:210-274makesdetailsoptional and uses one generic bag with an always-required free-formkind; OpenAPI does not correlate details withcode, does not restrictssh_error.stage, and does not requirerate_limited.retry_after_ms. A serializedinvalid_requestomitsdetails, contrary to §1.2's required body and per-code typing.Should-fix findings
Method-not-allowed contradicts the exhaustive registry.
crates/server/src/lib.rs:259-267returns HTTP 405 withinvalid_request, whose registered HTTP status is 400.POST /api/healthdemonstrates the inconsistent pair.agents:managedestructiveness is too coarse.crates/proto/src/permissions.rs:98-108marks the whole scope destructive, while §2.1 marks only revocation destructive; a plan records onlyScope, so enrollment and revocation cannot receive distinct treatment.Kind-specific DATA direction is absent from the state API.
ChannelStatestores noChannelKind, so the supplied registry cannot reject client-to-server DATA on logs/events/stats (§3.7); every consumer would need to recreate the rule.The normal encode API can emit invalid frames in release builds.
Frameexposes its header and payload, andFrame::encodeatcrates/proto/src/stream/frame.rs:343-353relies only ondebug_assert!; a release probe encodeddeclared=99with an actual one-byte payload. The obvious API should validate or make invalid state unrepresentable.Record decode hot paths copy byte-by-byte. Both decoders loop over each byte and call
extend_from_slice(&[byte])(records.rs:97-108,175-192); a complete log record's output did not alias its inputBytes. This conflicts with the repository's no per-byte copy rule. Criterion omits JSON-lines and channel-state hot paths.Grant shape validation is incomplete.
GrantClaims::validateaccepts SSH claims withoutownand ownership on unrelated kinds; nonce/signature checks validate only alphabet/length. A 43-character_nonce and 86-character_signature are accepted by the Rust type but fail base64 decoding, contrary to §3.9.RFC 8785 canonicalization lacks I-JSON input enforcement and project vectors. The dependency passed the RFC §3.2.2/§3.2.3 fixtures and all 24 Appendix B number vectors, and the project digest independently matched SHA-256. However,
canonical_plan_parametersreceives an already-parsedserde_json::Value, so duplicate-key evidence is lost and RFC 8785's I-JSON precondition cannot be enforced. The project has no official-vector or invalid-input tests. Source: https://www.rfc-editor.org/rfc/rfc8785.htmlIdentifier/hash/time schemas are weaker than their Rust intent. Generated ID and
ParameterHashschemas are unrestricted strings; plan timestamps are raw strings withoutdate-time. A scratchPlanaccepted invalid timestamps and a non-hash authorization string.RecordError::close_code()exposes the wrong close abstraction.crates/proto/src/stream/records.rs:234-237returns WebSocket 1000 for every record failure, while §3.7 requires channelCLOSE {error: invalid_request | record_too_large}.Verified clean behavior
Bytes::slice.Canonical verification evidence:
ISSUE UPDATE kayg/infraternal#2
review of: proto @
afec16a1c2verdict: reject
gates:
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: Vitest
PASS fast: OpenAPI and TypeScript client drift
PASS bench: JavaScript dependencies
PASS bench: build web app
PASS bench: criterion smoke (numbers from this VM are not reportable)
PASS bench: proto codec criterion smoke (numbers from this VM are not reportable)
findings:
tokens:manage.OpenParams.oneOfrejects legal params and does not bind params to kind.invalid_request, whose registry status is 400.agents:managecannot distinguish non-destructive enrollment from destructive revocation.Frame::encodecan emit malformed frames.RecordError::close_code()suggests normal WebSocket close instead of channel CLOSE errors.Orchestrator: review accepted in full (8 blocking, 9 should-fix). Added
method_not_allowedto the contract registry on dev (1d7b35d). Fix jobproto-fix(gpt-5.6-sol/medium) launched with every finding; regression tests for the two permission bugs first. Engine (#4) and auth (#5) branched fromafec16aand will rebase onto the fixed branch.Started review-fix job. Branch: proto. Requested base:
afec16a1c2. Merged dev at1d7b35d2d8to pick up method_not_allowed before implementation. Worktree: /mnt/hdd/infraternal/.worktrees/proto. Scope: all 17 independent-review findings, regression evidence, fast and bench gates.Security regression RED evidence at reviewed head
afec16a1c2(isolated detached worktree):cargo test -p infraternal-proto filtered_grants_cannot_mint_an_unfiltered_grant -- --nocapture→ FAILED: leftOk(()), rightErr(GrantOutsideEffective { scope: ContainersRead }).cargo test -p infraternal-proto token_mint_requires_effective_tokens_manage_and_never_mints_human_tokens -- --nocapture→ FAILED: mint without effectivetokens:managereturned success; this test also covers the no-token-mints-human invariant.Both regressions were written before implementation changes. §2.1 has now been updated first to split
agents:manageinto non-destructiveagents:enrolland destructiveagents:revoke.ISSUE UPDATE kayg/infraternal#2
branch: proto
head:
d74e53c16bdone:
docs/contracts.md§2.1 first: replacedagents:managewith non-destructiveagents:enrolland destructiveagents:revoke.tokens:manage; no token may mint a human token;ai_agentminting isai_agent-only.method_not_allowedfor 405.Bytes; no per-byte accumulation), removed the record/WebSocket close-code confusion, and added JSON-lines and channel-state benchmarks.docs/openapi.jsonandpackages/api-client/src/schema.ts; updated the package export surface for the discriminated OPEN type.blocking fail-before → pass-after evidence:
afec16a:cargo test -p infraternal-proto filtered_grants_cannot_mint_an_unfiltered_grant -- --nocaptureFAILED (left: Ok(()), expectedGrantOutsideEffective) →d74e53c: same command PASS (1 passed).afec16a:cargo test -p infraternal-proto token_mint_requires_effective_tokens_manage_and_never_mints_human_tokens -- --nocaptureFAILED (mint without effectivetokens:managereturned success; human→human was also accepted) →d74e53c: same command PASS (1 passed).afec16areview probe acceptedPINGandHELLO_OKon channel 1 →cargo test -p infraternal-proto connection_frames_are_rejected_on_ordinary_channels -- --nocapturePASS (1 passed).afec16areview probe producedremote_eof=Ok(Accepted)thendata_after_remote_eof=Ok(Accepted)→cargo test -p infraternal-proto peer_cannot_send_data_after_its_eof -- --nocapturePASS (1 passed).afec16areview probes returnedResourceExhaustedfor a duplicate at capacity and later accepted a capacity-refused ID →cargo test -p infraternal-proto capacity_does_not_mask_reuse_or_make_refused_ids_reusable -- --nocapturePASS (1 passed).afec16ageneratedOpenParams.oneOfmade{}match three branches and allowed mismatched kind/params →cargo test -p infraternal-server openapi_correlates_discriminated_payloads_and_constrains_wire_scalars -- --nocapturePASS (1 passed; five single-kind variants with bound named params).afec16areview deserialization probes accepted an invalid exec target, invalid/empty open key, persistent creation without an open key, andresume_fromwithoutsession_id→cargo test -p infraternal-proto open_rejects_invalid_targets_and_session_parameter_combinations -- --nocapturePASS (1 passed).afec16aserializedinvalid_requestwithoutdetails, used an uncorrelated generic bag, admitted invalid SSH stages, and did not require rate-limit delay →cargo test -p infraternal-proto required_details_and_retryability_are_validated_on_decode -- --nocaptureand the OpenAPI correlation test both PASS (1 passed each).other finding closure:
method_not_allowed; server regression test passes.ChannelStatestoresChannelKind; bulk DATA direction regression passes on client and tunnel hops.Frameheader/payload are private validated state with read-only accessors; ordinaryencodecan no longer receive caller-forged mismatched state._repros are rejected.ParameterHash, authorization hash, and RFC3339 timestamps are typed/constrained in Rust and OpenAPI.RecordError::close_code()removed;error_code()remains the channel CLOSE error abstraction.public API changes:
Scope::AgentsManage/agents:managereplaced byAgentsEnroll/agents:enrollandAgentsRevoke/agents:revoke.PermissionError::AiAgentCannotMintHumanreplaced byHumanTokensRequireInteractiveLogin;validate_token_mintnow enforces effectivetokens:manageand interactive-only human issuance.ChannelRegistry::open_localandaccept_opennow requireChannelKind;ChannelStateexposeskind.Frame.headerandFrame.payloadare private; useheader()andpayload().LogRecordDecoder::pushandJsonLinesDecoder::pushtake&Bytes;RecordError::close_coderemoved.OpenParamsremoved;OpenPayloadis the discriminated union.OpenKeyadded and persistent-session params use it.ApiError.detailsis required and typed viaApiErrorDetails; typed detail structs andSshErrorStageadded.ApiError::new/from_coderequire details;with_retryablepermits only registry-approved contextual overrides.ErrorCode::MethodNotAllowed/method_not_allowedadded.Plan.authorization_hashisParameterHash;created_at/expires_atareRfc3339Timestamp.canonical_parameter_hash_from_jsonadded for I-JSON-preserving input.OpenParams; generatedOpenPayloadis the discriminated union.commits:
c51fbaaproto: close token escalation pathse2eb47bproto: enforce channel state invariants7f75607proto: decode stream records by slice4f13d2eproto: constrain generated wire schemasec8e59fapi: regenerate constrained contract clientd0a02c6proto: keep hardened codecs lint-cleand74e53capi: export discriminated open payloadgates:
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: Vitest
PASS fast: OpenAPI and TypeScript client drift
PASS bench: JavaScript dependencies
PASS bench: build web app
PASS bench: criterion smoke (numbers from this VM are not reportable)
PASS bench: proto codec criterion smoke (numbers from this VM are not reportable)
open problems: none
Final verification amendment: graph-backed review caught that the schema-only error union initially emitted Rust variant spellings. Commit
9e8adc7adds a regression asserting every generated discriminator equals the stable snake_caseErrorCodespelling and regenerates OpenAPI/TypeScript. Final head:9e8adc769f84f0a5a64f5b69b6cb22b21e0609f8.Fresh gates at final head:
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: Vitest
PASS fast: OpenAPI and TypeScript client drift
PASS bench: JavaScript dependencies
PASS bench: build web app
PASS bench: criterion smoke (numbers from this VM are not reportable)
PASS bench: proto codec criterion smoke (numbers from this VM are not reportable)
Open problems: none.
Correction to the immediately preceding comment: the exact final head is
9e8adc7d5d8a11b31feae5392bbb8b316d96aa67(the short commit9e8adc7and all quoted gate evidence are unchanged).ISSUE UPDATE kayg/infraternal#2
branch: proto
head:
3ed3997f3aacceptance-review closure:
EngineAddress = ^h_[0-9A-HJKMNP-TV-Z]{26}/(docker|podman)/[^/]+$; events target is*orHostId; stats target isHostIdorEngineAddress. The OpenAPI regression asserts all three patterns, anddocs/openapi.jsonplus the TypeScript client were regenerated from source.9007199254740993), while accepting9007199254740992; lone-surrogate and invalid-UTF-8 strings are rejected. Primary source: https://www.rfc-editor.org/rfc/rfc8785.htmlResourceExhaustedis explicitly channel-local.FrameError::connection_close_code()returnsNonefor it, requiring the caller to sendOPEN_ERR resource_exhaustedwhile keeping the WebSocket open; connection-fatal errors still return 1009/1002.acceptance regression RED → GREEN:
cargo test -p infraternal-server openapi_correlates_discriminated_payloads_and_constrains_wire_scalars -- --nocaptureFAILED withEngineAddress pattern: left Null; at final head the same test PASS.cargo test -p infraternal-proto canonical_json_rejects_values_outside_the_ijson_domain -- --nocaptureFAILED because9007199254740993was accepted; at final head it PASS.cargo test -p infraternal-proto capacity_does_not_mask_reuse_or_make_refused_ids_reusable -- --nocaptureFAILED withleft: 1008, right: 1008; at final head it PASS.cargo test -p infraternal-proto canonicalization_matches_rfc_8785_number_vectors -- --nocapturePASS with all 24 finite Appendix B rows.persistent-session schema decision:
OpenKey,SessionId, and scalar field constraints, while Rust deserialization enforces the cross-field state rules:resume_fromrequiressession_id;open_keyandsession_idare mutually exclusive; persistent creation requiresopen_key; reattachment must remain persistent.oneOfobject shapes and would recreate the ambiguous generated-client union fixed by finding 6. Runtime deserialization is the single authoritative validator for these stateful field combinations; the OpenAPI remains exact for every independently expressible scalar/target constraint.additional commits:
dfe9a28proto: keep capacity refusal channel-local7a671feproto: enforce I-JSON numeric domainf231dc5api: publish exact stream target patterns3ed3997proto: check integer exactness without castspublic API impact amendment:
FrameError::close_code() -> u16is replaced byFrameError::connection_close_code() -> Option<u16>;Nonedenotes channel-local handling.EngineAddress, eventstarget, and statstargetstrings now carry exact regex constraints. Generated TypeScript string types are source-compatible.canonical_parameter_hash_from_jsonnow rejects integer lexemes outside the exact IEEE-754 binary64/I-JSON domain.gates:
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: Vitest
PASS fast: OpenAPI and TypeScript client drift
bench:
9e8adc7.open problems: none
Orchestrator acceptance verification on final head
3ed3997f3aab7d7e04d7104e4c2d5cd2a571ea57: worktree clean; graph rebuilt; accepted the target-schema, I-JSON-domain, full-vector, and channel-local capacity fixes. Both required gates were rerun on this exact head.Open problems: none. Issue remains open for owner/orchestrator merge handling.
Fix job
proto-fix(gpt-5.6-sol/medium) finished:Verify job
proto-verifyrunning.Verification start
branch: detached HEAD
base:
afec16ahead:
3ed3997f3ascope: step 6 fix verification only; no tracked-file edits or commits
worktree: /mnt/hdd/infraternal/.worktrees/proto-review
Independent verification findings (fix head
3ed3997f3a):execandsshOPEN params{cols:80,rows:24,persist:false,open_key:"01ARZ3NDEKTSV4RRFFQ69G5FAV"}.open_keyis contractually for persistent-session creation, so this is a blocks-merge validation hole. Other invalid targets, non-ULID keys/session IDs, missing persistent creation keys, reattach combinations, andresume_fromchecks rejected as expected.sub.user_id=u_01ARZ3NDEKTSV4RRFFQ69G5FAVandown=u_01ARZ3NDEKTSV4RRFFQ69G5FAWreturnedOk(()); the grant validator does not enforce that the session owner is the grant subject. Label later unless the owner treats this authorization invariant as merge-blocking.Scratch suite: 15 checks passed; the two assertions above intentionally failed on the observed gaps. No tracked files changed.
ISSUE UPDATE kayg/infraternal#2
verify of: proto @
3ed3997f3averdict: do not merge
gates:
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: Vitest
PASS fast: OpenAPI and TypeScript client drift
PASS bench: JavaScript dependencies
PASS bench: build web app
PASS bench: criterion smoke (numbers from this VM are not reportable)
PASS bench: proto codec criterion smoke (numbers from this VM are not reportable)
findings:
1 → fixed: filtered grants cannot authorize an unfiltered grant across mixed role/token tag cases.
2 → fixed: human token requests are rejected and programmatic minting requires unfiltered tokens:manage on both sides.
3 → fixed: PING/PONG and HELLO/HELLO_OK are rejected on live and unknown nonzero channels.
4 → fixed: DATA after the sender's EOF is rejected while DATA in the opposite direction remains allowed.
5 → fixed: duplicate IDs are reported as reuse and capacity-refused IDs remain retired after capacity recovers.
6 → fixed: generated OpenPayload is a five-variant discriminated oneOf with kind-bound params; legal empty params parse.
7 → not fixed: exec and ssh accept persist:false plus open_key, which is invalid for ephemeral OPEN creation.
8 → fixed: errors serialize required per-code details and reject missing, wrong, or unknown detail fields.
9 → fixed: HTTP 405 returns method_not_allowed and the Allow header.
10 → fixed: agents:enroll and agents:revoke are distinct; only revoke is destructive.
11 → fixed: DATA direction is enforced for logs/events/stats on client and tunnel hops; exec/ssh remain bidirectional.
12 → fixed: public Frame construction is invariant-preserving and invalid fixed-width/oversized frames are rejected.
13 → fixed: complete log records alias input bytes and codec benchmarks cover log, JSON, frame, and channel hot paths.
14 → not fixed: strict base64url checks pass, but own may differ from sub.user_id; the validator returns Ok for that mismatch.
15 → fixed: independent nested duplicate, I-JSON numeric/string, canonical ordering, and hash probes pass.
16 → fixed: generated ID, engine-address, hash, and timestamp schemas are constrained.
17 → fixed: RecordError exposes channel error_code mapping instead of a WebSocket close_code.
blocks-merge: finding 7 (persistent OPEN accepts open_key with persist:false)
later: finding 14 (grant own is not bound to sub.user_id)
No tracked files changed; worktree remains clean and detached.
Verify job
proto-verify(gpt-5.6-luna/max): 15/17 fixed; still open 7 (open_key accepted with persist:false, blocks merge) and 14 (grant own may differ from sub user, later).Orchestrator: per the one-review-one-verify rule, one final small fix job
proto-fix2fixes both with fail-first regression tests; the orchestrator checks its evidence and merges without another review round.Started final fix job on branch proto at base
3ed3997f3a. Scope: reject non-persistent OPEN requests carrying open_key; reject grants whose own claim differs from sub.user_id. I will add regressions, reproduce both failures on the base, fix, and run scripts/verify fast.Base reproductions on
3ed3997f3awith regression tests added first: cargo test -p infraternal-proto open_rejects_open_key_for_non_persistent_exec_session -> FAILED (assertion: accepted persist:false plus open_key); cargo test -p infraternal-proto grant_rejects_owner_mismatch_with_subject_user -> FAILED (assertion: accepted own different from sub.user_id).ISSUE UPDATE kayg/infraternal#2
branch: proto
base:
3ed3997head:
feedcf2464fixed: reject persist:false OPEN requests carrying open_key (open_rejects_open_key_for_non_persistent_exec_session); reject grant own claims that differ from sub.user_id (grant_rejects_owner_mismatch_with_subject_user)
gates:
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: Vitest
PASS fast: OpenAPI and TypeScript client drift
Final fix
proto-fix2(feedcf2): both remaining findings fixed with regression tests that failed on3ed3997and pass now; the orchestrator read the diff. Merged to dev. Step 6 done.Engine (#4) and auth (#5) branched from
afec16aand must rebase onto dev before their merges (public API changes are listed above).