Skip to content

test: bring Response.java StubResponse key sets under the schema drift check #315

Closed

Description

@EtaCassiopeia

Problem

rift-java-core/src/main/java/io/github/achirdlabs/rift/model/Response.java keeps the StubResponse wire key sets inline, one Set.of(...) per variant (e.g. Set.of("is", "_behaviors", "behaviors", "_rift", "repeat") around lines 42, 63, 91, 115, 142, and 162-191), rather than in named constants. SchemaConformanceTest.modeledKeysAreKeysTheEngineReads (added in #314 for #300) only checks keys held in a named MODELED_KEYS set, so these inline keys are not covered. A key the engine never reads could be modeled in Response and no gate would notice.

Evidence

Proposed fix

  1. Lift the per-variant key sets in Response.java into named private static final constants (one per variant, or a single union MODELED_KEYS).
  2. Add Response (or each variant) to MODELED_KEYS_DEFS in SchemaConformanceTest, mapped to StubResponse, so the drift check covers it.

No behaviour change and no public API change.

Should this exist?

SETTLED: this is test-coverage hardening for the drift check introduced in #300. It adds no new public surface.

Provenance

Found by an agent while reviewing PR #314 (issue #300). Out of scope for that work, filed as a follow-up.


Triage

Verdict: build now. This is a test-only hardening with no engine gate. The premise is correct, with one design correction: the constants cannot live on Response itself, because interface fields are implicitly public. Route: Sonnet.

Engine facts (checked against v0.22.0 @ 3f47706; origin/master @ 2d27036 is identical for this def)

  • $defs/StubResponse (sdk-conformance/schema/imposter.schema.json:1499) is additionalProperties: false, with properties _behaviors, _mode, _rift, behaviors, body, fault, headers, inject, is, proxy, repeat, statusCode. origin/master declares the same property set; its 6-line schema diff touches other defs.
  • The schema is generated from StubResponseRaw (crates/rift-mock-core/src/imposter/types.rs:1029-1090, wired in at :3068), so the 12 keys are exactly the serde fields the engine reads.

rift-java today

  • Response.java repeats each variant's key set inline, twice: once in the canonical constructor and once in read().
    • Is: :42, :162
    • Proxy: :63, :168
    • Inject: :91, :174
    • Fault: :115, :180
    • RiftScript: :142, :191
    • Behavior-key subsets: :200 (flat form), :277 (withoutBehaviorKeys)
  • Response.java is the only model file left with inline sets. Every other model type has a private static final Set<String> MODELED_KEYS (for example Stub.java:36, IsResponse.java:36).
  • SchemaConformanceTest.java:58-68 maps model types to schema defs. Response is not in that map. modeledKeys() (:179-183) reflects Class.forName("io.github.achirdlabs.rift.model." + type).getDeclaredField("MODELED_KEYS").
  • No drift exists today: every inline key is in StubResponse. The gap is coverage only.

Decision

  • Build it. It adds no public surface and changes no behaviour, so the should-question is settled.
  • Correction to fix step 1: a "single union MODELED_KEYS" on Response is not possible as a private constant. Response is an interface, so any field on it is public static final, which would add public API. Put a private static final Set<String> MODELED_KEYS in each nested record instead. Records allow private static fields, and Response.read() can read them through nestmate access.
  • The 5 variant constants are what the test needs. Also lift the repeated behavior keys into a package-private-by-nesting constant. That removes the :200/:277 literals and keeps the five sets in step.

Design

record Is(...) implements Response {
    private static final Set<String> MODELED_KEYS = Set.of("is", "_behaviors", "behaviors", "_rift", "repeat");
    public Is { ...; JsonSupport.rejectModeledExtraKeys(extra, MODELED_KEYS, "is response"); ... }
}
record Proxy(...)      { private static final Set<String> MODELED_KEYS = Set.of("proxy",  "_behaviors", "behaviors", "repeat"); }
record Inject(...)     { private static final Set<String> MODELED_KEYS = Set.of("inject", "_behaviors", "behaviors", "repeat"); }
record Fault(...)      { private static final Set<String> MODELED_KEYS = Set.of("fault",  "_behaviors", "behaviors", "repeat"); }
record RiftScript(...) { private static final Set<String> MODELED_KEYS = Set.of("_rift",  "_behaviors", "behaviors", "repeat"); }

// read():  JsonSupport.extraFields(obj, Is.MODELED_KEYS)  etc.

Test map (Class.forName takes the binary name, so the key is Response$Is):

MODELED_KEYS_DEFS.put("Response$Is", "StubResponse");
MODELED_KEYS_DEFS.put("Response$Proxy", "StubResponse");
MODELED_KEYS_DEFS.put("Response$Inject", "StubResponse");
MODELED_KEYS_DEFS.put("Response$Fault", "StubResponse");
MODELED_KEYS_DEFS.put("Response$RiftScript", "StubResponse");

Optional: the flat form reads IsResponse.MODELED_KEYS at stub-response level (Response.java:182,200). Covering it would mean also checking IsResponse against StubResponse, but MODELED_KEYS_DEFS is keyed by type, so that needs a separate assertion or a list of pairs. Leave it out unless it stays a one-liner.

Implementation plan

  • rift-java-core/src/main/java/io/github/achirdlabs/rift/model/Response.java: add per-record MODELED_KEYS, and replace the 10 inline Set.of uses at :42,63,91,115,142,162,168,174,180,191. Optionally add a shared behavior-key constant for :200,:277. Keep the existing Javadoc. No signature changes.
  • rift-java-conformance/src/test/java/io/github/achirdlabs/rift/conformance/SchemaConformanceTest.java: add the 5 Response$… entries to the static block at :60-68.

Tests

  • Unit (core): the existing response tests must pass unchanged: ResponseFieldPreservationTest, ResponseLevelRepeatTest, FlatResponseFormTest, ProxyResponseRoundTripTest, FaultResponseRoundTripTest, RiftScriptResponseRoundTripTest. Since behaviour does not change, no new core test is needed.
  • Conformance: run modeledKeysAreKeysTheEngineReads against the 0.22.0 corpus, using RIFT_CORPUS_ROOT=<…>/sdk-conformance-v0.22.0 or -Drift.corpus.root. Run it with RIFT_IT=1 in the CI lane. Without a corpus the test skips, so a skip does not count as a pass.
  • Prove the gate bites: temporarily add a bogus key (for example "isx") to Is.MODELED_KEYS. The test must fail with Response$Is=[isx]. Revert the key afterwards.
  • Embedded and testcontainers ITs are not relevant (no wire change).

Acceptance criteria

  • Response.java has no inline Set.of(...) key literals. Each variant's constructor and its read() branch share one private static final MODELED_KEYS.
  • SchemaConformanceTest.MODELED_KEYS_DEFS maps all 5 Response variants to StubResponse, and the test passes against the rift 0.22.0 schema.
  • There is no public API or binary change. japicmp/revapi, if configured, is clean, and no new public field exists on Response.
  • rift-scala impact: none. The new constants are private and the wire output is unchanged.

Gating

None. Everything can land now against the pinned 0.22.0 corpus, and StubResponse is unchanged on rift origin/master.

Activity

  1. added
    agent-foundFinding discovered by automated agent review
    needs-triageIssue awaiting triage and prioritization
    ready-to-implementTriaged: root cause confirmed, design and implementation steps recorded
    and removed
    needs-triageIssue awaiting triage and prioritization
    on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-foundFinding discovered by automated agent reviewready-to-implementTriaged: root cause confirmed, design and implementation steps recorded

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions