Skip to content

[default values] Tables: add a server-stamped per-table config map to GetTableResponseBody and capture it client-side#644

Merged
cbb330 merged 5 commits into
linkedin:mainfrom
cbb330:chbush/runtime-policy-response
Jul 4, 2026
Merged

[default values] Tables: add a server-stamped per-table config map to GetTableResponseBody and capture it client-side#644
cbb330 merged 5 commits into
linkedin:mainfrom
cbb330:chbush/runtime-policy-response

Conversation

@cbb330

@cbb330 cbb330 commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a generic, server-stamped, per-table client config map to GetTableResponseBody and captures it in OpenHouseTableOperations so subclasses can read it. It follows the Iceberg REST LoadTableResponse.config convention — a Map<String,String> of client-side behavior overrides the server controls at runtime, without re-rolling the slow-to-upgrade Java client fleet. No behavior change: the map is null/empty until a server stamps it, and the base client has nothing to act on.

Changes

  • Client-facing API Changes
  • Internal API Changes
  • Bug Fixes
  • New Features
  • Tests

Client-facing API Changes

  • READ_ONLY, nullable config field (Map<String,String>) on GetTableResponseBody, modeled on the Iceberg REST load-table config. Namespaced keys (e.g. openhouse.read-bridge); clients ignore keys they do not understand.
  • Why a response field: doRefresh already fetches a live GetTableResponseBody on every table load (and on commit responses) but discarded all but getTableLocation() — it is the natural, already-present, server-controlled, zero-staleness delivery channel. The generated client sets FAIL_ON_UNKNOWN_PROPERTIES = false, so a new response field cannot break older clients (and unknown config keys are simply carried). Same additive, nullable, READ_ONLY pattern as sortOrder etc.

Internal API Changes

  • OpenHouseTableOperations.doRefresh keeps the full response and stashes config in an AtomicReference, exposed to subclasses via the new protected currentConfig(). READ_ONLY + side-channel: never sent back on writes.
  • TablesMapper (the table DTO serializer into gettableresponsebody) ignores config (@Mapping(target = "config", ignore = true)) — it is stamped separately, not sourced from TableDto.

New Features

  • A flat string map keyed by namespaced keys means new features become new config entries rather than an API/schema change or client regen.

Testing Done

  • Added new tests.

Compile/codegen verified (:services:tables, :client:tableclient, :integrations:java:iceberg-1.2:openhouse-java-runtime); the client regenerates with getConfig() returning Map<String,String>. OpenHouseTableOperationsTest covers: currentConfig() null before refresh; doRefresh captures/clears config; the REST-style string map deserializes from a response and tolerates unknown fields.

Stack

#644 (channel)#645 (read-bridge mechanism) → li-openhouse #2166 (li column-default source from avro.schema.literal)

This is the substrate PR — intentionally behaviorless on its own.

Next steps (this PR)

This PR delivers only the channel; it stays inert until two further pieces exist, both required, and both delivered by the next PR in the stack:

cbb330 and others added 3 commits June 27, 2026 21:41
Add a server-stamped, READ_ONLY runtimePolicy field (RuntimePolicy +
PolicyDirective response components) as a forward-compatible channel for
delivering per-table client directives at runtime, without re-rolling the
Java client fleet. The read-bridge gate is its first intended consumer.

The field is null by default, so there is no behavior change: the server
PolicyEngine stamp and the client-side reader are follow-ups. Typed
envelope + opaque per-feature payload means new features are new map
entries rather than API/schema changes; READ_ONLY keeps it off the write
path and out of persisted metadata.
doRefresh now keeps the full GetTableResponseBody (instead of mapping it
straight to the table location) and stashes the server-stamped
runtimePolicy in an AtomicReference, exposed to subclasses via the new
protected currentRuntimePolicy(). Absent policy => null, the safe default.

This makes the per-table runtime policy available at the loadMetadata
seam so subclasses can gate read-time behavior (e.g. the li-openhouse
column-default overlay) without a client re-roll. Behaviorless on its own:
the base client has no overlay to gate, and the policy is read-only,
never sent back on writes.
The "runtime policy" / "directive" naming was domain-specific and collided
with the existing table-governance Policies object (it forced a Javadoc
disclaimer). Rename the channel to plain-language feature-flag terms that
state what it does: the server controls per-table client behavior at runtime.

  RuntimePolicy            -> FeatureFlags   (envelope)
  PolicyDirective          -> FeatureFlag    (per-feature entry)
  .directives map          -> .flags
  .policyVersion           -> .version
  GetTableResponseBody.runtimePolicy        -> featureFlags
  OpenHouseTableOperations.currentRuntimePolicy() -> currentFeatureFlags()

Behavior, READ_ONLY/side-channel semantics, and the typed-envelope +
opaque-payload contract are unchanged; this is a pure rename. The generated
client model regenerates as FeatureFlags/FeatureFlag. Cheapest moment to
rename: the field is still null end-to-end and no consumer has pinned to the
generated type yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cbb330 cbb330 changed the title Tables: add server-stamped RuntimePolicy to GetTableResponseBody and capture it client-side Tables: add server-stamped FeatureFlags to GetTableResponseBody and capture it client-side Jun 28, 2026
Add OpenHouseTableOperationsTest coverage for the runtime feature-flag
channel:

  - currentFeatureFlags() is null before any refresh (safe default)
  - doRefresh captures the server-stamped featureFlags
  - absent featureFlags on the response => null
  - a later refresh that stops stamping clears the stale value (the held
    policy is the latest snapshot, never sticky)
  - the typed envelope + opaque payload deserializes from a response, and
    unknown envelope/flag fields are tolerated (FAIL_ON_UNKNOWN_PROPERTIES)

Co-authored-by: Cursor <cursoragent@cursor.com>
@cbb330 cbb330 marked this pull request as ready for review June 28, 2026 07:43
@cbb330 cbb330 changed the title Tables: add server-stamped FeatureFlags to GetTableResponseBody and capture it client-side Tables: add a server-stamped per-table config map to GetTableResponseBody and capture it client-side Jun 29, 2026
@cbb330 cbb330 changed the title Tables: add a server-stamped per-table config map to GetTableResponseBody and capture it client-side [default values] Tables: add a server-stamped per-table config map to GetTableResponseBody and capture it client-side Jun 29, 2026
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource (+ NoColumnDefaults no-op default): the single
  open-source/closed-source seam (field-id -> Iceberg single-value JSON).
- TableConfigResolver + ReadBridgeConfigResolver: OSS-owned resolver that owns
  the config key (openhouse.read-bridge) and payload contract, returning the
  per-table config map the handler stamps via withConfig.
- OpenHouseTableOperations.loadMetadata: the client gate reads the read-bridge
  config key and overlays per-column initial-defaults; the overlay transform
  (applyColumnDefaults) is a marked TODO so the gate runs end-to-end while
  behavior is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource (+ NoColumnDefaults no-op): the single
  open-source/closed-source seam (field-id -> Iceberg single-value JSON).
- TableConfigResolver + ReadBridgeConfigResolver: the server-side encoder that
  owns the config key (openhouse.read-bridge) and the payload contract.
- ReadBridgeConfig (client decoder; mirror of the resolver) + ReadBridgeOverlay
  (the overlay transform, a marked TODO) keep OpenHouseTableOperations slim — its
  loadMetadata just decodes the config and applies the overlay. ReadBridgeConfig
  is unit-tested in isolation (ReadBridgeConfigTest).

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource (+ NoColumnDefaults no-op): the single
  open-source/closed-source seam (field-id -> Iceberg single-value JSON).
- TableConfigResolver + ReadBridgeConfigResolver: the server-side encoder that
  owns the config key (openhouse.read-bridge) and the payload contract.
- ReadBridgeConfig (client decoder; mirror of the resolver, unit-tested in
  isolation) + ReadBridge (the read-time bridge entry point that applies the
  decoded behavior; the column-default overlay is a marked TODO and the place
  further V3 features get backported) keep OpenHouseTableOperations slim — its
  loadMetadata just calls ReadBridge.apply(raw, currentConfig()).

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgeConfigResolver: the server-side encoder that owns the config key
  (openhouse.read-bridge) and payload contract, wired into the handler.
- ReadBridge (client): decodes that config and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Mirror of the server encoder,
  unit-tested in isolation. Keeps OpenHouseTableOperations.loadMetadata to one
  delegating call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgePayload: the typed payload of the read-bridge config entry. The
  server (ReadBridgeConfigResolver) and client (ReadBridge) ser/deser it with
  Jackson, so neither hand-crafts JSON; field names are declared once via
  @JsonProperty. Mirrored across the module split.
- ReadBridgeConfigResolver: server-side encoder that owns the config key
  (openhouse.read-bridge), wired into the handler.
- ReadBridge (client): decodes the payload and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ntion)

Conform the channel wire to Iceberg REST LoadTableResponse.config: replace the
bespoke featureFlags envelope on GetTableResponseBody with a config
Map<String,String>, and capture it client-side as currentConfig().

- Drop the typed FeatureFlags/FeatureFlag models; the wire is a string map
  (READ_ONLY, nullable). Unknown keys are ignored by clients.
- OpenHouseTableOperations captures `config` (currentConfig()) instead of a
  typed envelope.
- TablesMapper ignores `config` (stamped separately by the API handler).

Behaviorless on its own; the server stamp + client consumer land in the
read-bridge PR stacked on this one.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cbb330 cbb330 force-pushed the chbush/runtime-policy-response branch from 7c276f0 to fdfaf48 Compare June 29, 2026 22:26
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgePayload: the typed payload of the read-bridge config entry; server
  (ReadBridgeConfigResolver) and client (ReadBridge) ser/deser it with Jackson,
  so neither hand-crafts JSON. Mirrored across the module split.
- ReadBridgeConfigResolver: server-side encoder that owns the config key
  (openhouse.read-bridge), wired into the handler.
- ReadBridge (client): decodes the payload and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgePayload: the typed payload of the read-bridge config entry; server
  (ReadBridgeConfigResolver) and client (ReadBridge) ser/deser it with Jackson,
  so neither hand-crafts JSON. Mirrored across the module split.
- ReadBridgeConfigResolver: server-side encoder that owns the config key
  (openhouse.read-bridge), wired into the handler.
- ReadBridge (client): decodes the payload and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 29, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgeConfigResolver: server-side encoder that stamps each default as a
  flat namespaced config entry (openhouse.read-bridge.column-default.<fieldId> =
  single-value JSON) — no envelope/POJO; the config map carries the structure.
- ReadBridge (client): decodes those entries and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jun 30, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgeConfigResolver: server-side encoder that stamps each default as a
  flat namespaced config entry (openhouse.read-bridge.column-default.<fieldId> =
  single-value JSON) — no envelope/POJO; the config map carries the structure.
- ReadBridge (client): decodes those entries and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jul 1, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgeConfigResolver: server-side encoder that stamps each default as a
  flat namespaced config entry (openhouse.read-bridge.column-default.<fieldId> =
  single-value JSON) — no envelope/POJO; the config map carries the structure.
- ReadBridge (client): decodes those entries and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cbb330 cbb330 merged commit 99133b0 into linkedin:main Jul 4, 2026
1 check passed
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jul 4, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgeConfigResolver: server-side encoder that stamps each default as a
  flat namespaced config entry (openhouse.read-bridge.column-default.<fieldId> =
  single-value JSON) — no envelope/POJO; the config map carries the structure.
- ReadBridge (client): decodes those entries and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
cbb330 added a commit to cbb330/openhouse that referenced this pull request Jul 4, 2026
Adds the open-source read-bridge feature on top of the per-table `config`
channel (linkedin#644):

- ColumnDefaultsSource: the single open-source/closed-source seam (field-id ->
  Iceberg single-value JSON). Open-source default is a no-op lambda in ApiConfig;
  a deployment overrides this bean (e.g. li-openhouse, from avro.schema.literal).
- ReadBridgeConfigResolver: server-side encoder that stamps each default as a
  flat namespaced config entry (openhouse.read-bridge.column-default.<fieldId> =
  single-value JSON) — no envelope/POJO; the config map carries the structure.
- ReadBridge (client): decodes those entries and applies the overlay at
  metadata-load time (the column-default transform is a marked TODO, and the
  place further V3 features get backported). Keeps loadMetadata to one call.

Behaviorless until a ColumnDefaultsSource is supplied; fail-closed throughout.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

2 participants