From ed68e370506c6f2739630b9e3330f472cf618f97 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Fri, 11 Sep 2026 20:49:16 +0000 Subject: [PATCH 1/2] fix(lease): a present-but-empty schema field is refused, not read as the oldest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `parse_body` splits each line on `": "`, so a line written `schema:` — the field present, the value empty — never reaches the arm that parses the major. It fell through the empty-value block's `_ => {}`, left `schema` at `None`, and the reading below turns `None` into `BODY_SCHEMA`. A body whose writer disagrees with us about what the field IS was parsed loosely and acted on, which is the exact failure the major was added to stop. `None => BODY_SCHEMA` is right for a body written BEFORE the field existed. It is wrong for one carrying the field and saying nothing in it, and the two are indistinguishable once the line is dropped on the floor. The existing case asserts this property and passed throughout: its fixture is `schema: tomorrow`, which DOES contain `": "`, reaches the match arm, fails `parse::()`, and takes the refusal path. Two spellings of one case took different branches and the suite pinned the branch that already worked. `Body::render` always writes a number, so nothing in this crate emits a bare `schema:`; the exposure is a future writer, a hand-edited ref, or a partial write. It is worth closing because it is a fail-open arm inside a mechanism whose entire purpose is to fail closed. `//MUTANT empty-schema-reads-as-oldest` reverts the new arm. Measured: the declared case dies under it and the two twins — the absent-field hinge and a good `schema: 1` body — stay green, so the refusal is keyed to the emptiness rather than to the field being present. Closes CLOUD-1792 --- crates/batten/src/lease.rs | 74 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/crates/batten/src/lease.rs b/crates/batten/src/lease.rs index 1e8daea1d..0df8b2216 100644 --- a/crates/batten/src/lease.rs +++ b/crates/batten/src/lease.rs @@ -1246,6 +1246,19 @@ impl Body { /// A body with no `expires:` line yields `None` for the same reason rather than /// defaulting here — the default belongs to the caller that knows its own TTL, and /// a parser inventing one would report a lease it could not read as one it could. +/// +/// **A present-but-empty `schema:` is not an absent one.** The empty-value block +/// below carries an arm for it so it takes the same refusal an unparseable major +/// takes; without that arm it falls to `_ => {}`, `schema` stays `None`, and the +/// reading turns `None` into [`BODY_SCHEMA`] — a body whose writer disagrees with +/// us about what the field IS, parsed loosely and acted on, which is the exact +/// failure the major was added to stop (CLOUD-1792). +// The arm, reverted. The old spelling stayed green under the suite that already +// existed because `schema: tomorrow` contains `": "` and reaches the match arm — +// two spellings of one case taking different branches, and the test picked the +// branch that already worked. +//MUTANT-SUITE crates/batten/src/lease.rs +//MUTANT empty-schema-reads-as-oldest|s@ "schema" => {}@ "schema" => {}@|a_body_carrying_an_empty_major_is_refused #[must_use] pub fn parse_body(object: &[u8]) -> Option { let text = String::from_utf8_lossy(object); @@ -1273,6 +1286,13 @@ pub fn parse_body(object: &[u8]) -> Option { "next" => body.next.clear(), "stand-down" => body.stand_down.clear(), "progress" => body.progress.clear(), + // `schema:` with nothing after it is NOT the absent field. + // Absence means "written before the field existed", and reads + // as major 1; a field that is present and says nothing was + // written by something that disagrees with us about what the + // field IS, which is the whole reason the major exists. Route + // it to the same refusal an unparseable value takes. + "schema" => schema = Some(None), _ => {} } } @@ -4699,6 +4719,60 @@ mod tests { assert_eq!(parse_body(&object.body), None); } + /// THE CASE. A `schema:` carrying nothing is refused, not read as the oldest. + /// + /// This spelling has no `": "` to split on, so it never reaches the arm + /// `an_unreadable_major_is_refused_rather_than_treated_as_the_oldest` + /// exercises. Before the empty-value arm existed it fell to `_ => {}`, left + /// `schema` at `None`, and was read as major 1 — the loose parse the field + /// exists to refuse, reached by a second spelling of the same case. + #[test] + fn a_body_carrying_an_empty_major_is_refused() { + let object = lease_object( + "land-lock\nschema:\nholder: a\nexpires: 1700000060\nnonce: bb\n", + 1_700_000_000, + ) + .expect("mint"); + assert_eq!( + parse_body(&object.body), + None, + "a present-but-empty version field is a writer disagreeing about the \ + field, not a body written before it existed" + ); + } + + /// The compatibility hinge, unbroken: NO `schema:` line still reads as 1. + /// + /// The anti-vacuity twin. Without it the case above is satisfied by refusing + /// every body, which would stop the whole fleet the moment this build met a + /// lease minted before the field was added. + #[test] + fn a_body_with_no_major_at_all_still_reads_as_the_oldest() { + let object = lease_object( + "land-lock\nholder: a\nexpires: 1700000060\nnonce: bb\n", + 1_700_000_000, + ) + .expect("mint"); + let body = parse_body(&object.body).expect("an older body is still readable"); + assert_eq!(body.schema, BODY_SCHEMA); + } + + /// And a body that states a major this build speaks is still acted on. + /// + /// The second twin: refusal must be keyed to the emptiness, not to the field + /// being present. + #[test] + fn a_body_stating_a_major_we_speak_still_parses() { + let object = lease_object( + "land-lock\nschema: 1\nholder: a\nexpires: 1700000060\nnonce: bb\n", + 1_700_000_000, + ) + .expect("mint"); + let body = parse_body(&object.body).expect("a body this build speaks is readable"); + assert_eq!(body.schema, BODY_SCHEMA); + assert_eq!(body.holder, "a"); + } + /// Every mint stamps THIS build, and never inherits a predecessor's. /// /// `reservation` is the discriminating one: a WAITER mints it onto the From 728aa8484fd972a20f480d5f8f401b97e167a6a9 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Fri, 11 Sep 2026 20:49:32 +0000 Subject: [PATCH 2/2] chore(settings): pre-approve the read-only PR read used by every landing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `mcp__github__pull_request_read` is the one MCP call this session made repeatedly (8 times) that was neither already in `permissions.allow` nor covered by a broader entry: it is how a land reads CI and review state off a PR, and it prompted on every one. It is read-only, so it widens nothing a land could not already observe. No other candidate survived the scan — every Bash hit was already subsumed by an existing wildcard or auto-allowed by the harness, the whole `mcp__serena` server is allowed, and Linear is granted at the connector. Nothing was removed, and nothing was added to `deny` or `ask`. Whether this grant actually reaches the remote harness is CLOUD-1426's question, not this commit's answer — the entry is written where the repo keeps its grants and that row owns the layer. Refs: CLOUD-1426 --- .claude/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.claude/settings.json b/.claude/settings.json index 7fc3ae0fc..9c8f4ae56 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -139,7 +139,8 @@ "mcp__serena__replace_in_files", "mcp__serena__replace_symbol_body", "mcp__serena__safe_delete_symbol", - "mcp__serena__write_memory" + "mcp__serena__write_memory", + "mcp__github__pull_request_read" ], "deny": [ "mcp__Claude_Code_Remote__send_later",