Repository navigation
Widen the provisioning tools: expires_at and keys on create, mode on load, and mark the destructive set #91
Description
Corrected 1 Sep 2026. This issue originally opened by claiming all three changes were
available onhotdata_framework>=0.10.0with "no SDK bump, no upstream PR". That is wrong
for two of them, and the error was inherited fromdocs/ddl-tools-plan.md:5, whose
self-containment claim rests on the same false premise. Corrected inline below; see the
comments for the version table that pinned it down.
Three changes. mode is free today; format and result_id are not — see the correction
under item 2. The tool signatures are narrower than the calls they make, but in two cases the
framework client is narrower than the SDK model as well.
Context: an internal audit (18 Aug) counted 3 tools exposed against 8 framework capabilities
available but unwrapped. This issue is the subset an agent that provisions its own storage needs
first; the broader lifecycle question stays in #61.
1. hotdata_create_managed_database drops four parameters
The tool takes (name, schema_name, tables). The framework call underneath takes
keys, expires_at, partition_by and sorted_by as well.
Two of those are load-bearing for an agent that provisions its own storage:
expires_at— accepts RFC 3339 or a relative"24h"/"7d". Without it, every database an
agent creates lives forever, and lifetime becomes a cleanup script instead of a property. This is
the whole answer to "who deletes investigation number 4,281".keys— declares each table's natural key, which is what makes the key-based load modes
legal on it. A table declared through the tool today is keyless, soupsert,updateand
deleteloads are rejected against it for the rest of its life.
partition_by / sorted_by are permanent once a table exists — there is no ALTER path
anywhere in the API, and the only undo is delete-and-reload, which currently burns the table name
(#36). Whatever the tool description says about these two should state that outright, in the same
"state the consequence, not just the parameter" style as today's load tool.
2. hotdata_load_managed_table hardcodes mode="replace"
databases.load_managed_table never passes mode, so the framework default applies and
append, upsert, update and delete are unreachable from a tool. An agent cannot top up a
table it already loaded, and cannot make a re-run idempotent.
format and result_id are not reachable from the framework client on any version.
hotdata_framework/client.py:396 builds LoadManagedTableRequest(mode=…, upload_id=…, key=…)
— three fields — and load_managed_table is the only load path on the client. Checked across
0.10.0, 0.11.0, 0.12.0, 0.12.1 and 0.13.0: absent from all five. So these two need a framework
change, or a raw-DatabasesApi bypass, and are not in the same "pure wrapping" bucket as
mode.
A working bypass already exists downstream, if it helps whoever picks this up: the
connection-scoped route fails with X-Database-Id header is required, and
DatabasesApi(client.api).load_database_table(database_id=…, var_schema=…, table=…, load_managed_table_request=LoadManagedTableRequest(mode="replace", result_id=…)) works. That
said, a framework change is the cleaner fix in a package that goes through the client
everywhere else.
The underlying LoadManagedTableRequest accepts both, and they are worth having:
formatremoves a pyarrow step from every caller materialising data it already holds (this is
gap 2 in Provisioning boundary: agent-driven database, table, and index lifecycle #61).result_idpublishes a persisted query result as a table's contents — effectively
CREATE TABLE AS SELECT, including across an attached catalog. Worth flagging: the DDL memo
recorded query materialization as "found nowhere, needs upstream design" after looking in
ResultsApi,SavedQueriesApiandQueryRunsApi. It exists — it is just on the load endpoint.
3. Mark the destructive tools so interrupt_on can be configured
HumanInTheLoopMiddleware(interrupt_on=…) is keyed by tool name, so a developer wiring approval
has to know which HDLC tool names are mutating. Today that is inferred from naming.
- Export
DESTRUCTIVE_TOOL_NAMESfrom the package so it can be passed straight through. - Set
metadata={"destructive": True}on the mutating tools —BaseToolsupportstagsand
metadata, settable viaStructuredTool.from_function, and it echoes MCP'sdestructiveHint.
Worth stating plainly in the docs while doing this: management_tools=False is not a safety
flag (the source already says so), and "read-only" currently appears only in tool descriptions
written for the model — there is no validator on the SQL path in this library. DDL through SQL is
blocked by runtimedb's write_guard.rs rather than by anything here, and that guard does not
cover the Databases/Indexes/Jobs REST endpoints.
Activity
rohan-hotdata commentedon Sep 1, 2026
Correction: "no SDK bump, no upstream PR" holds for items 1 and 3, but not for half of item 2
Checked while sequencing this against a downstream consumer. The framing at the top — "All of it is
already available on
hotdata_framework>=0.10.0, which HDLC already requires" — is the load-bearingclaim for scoping this issue, and it is true for most of it and false for two parameters.
What is genuinely just a widening
Verified against the installed 0.12.1:
So item 1 (
keys,expires_at,partition_by,sorted_by) and item 2'smodeare exactly asdescribed — the framework already takes them and HDLC's tool signatures are simply narrower. Item 3
touches only this package. No upstream work for any of that.
What is not reachable
formatandresult_idare not on the framework client. They exist on theLoadManagedTableRequestmodel, which is what the issue quotes, butHotdataClient.load_managed_tablehardcodes the fields it forwards:
Neither parameter appears in the signature, and it is the only load path on the client — there is no
sibling method that reaches them.
Checked across the whole supported range rather than only what happens to be installed:
hotdata-frameworkformatresult_idpyproject.toml)What this changes
Item 2 splits in two, and the halves have different costs:
mode— pure HDLC widening, ship whenever.formatandresult_id— need ahotdata-frameworkchange, or HDLC reaching past the clientto
DatabasesApi.load_database_tabledirectly. The second is worth resisting: this package goesthrough the framework client everywhere else, and one raw-API call would be the only place that
does not.
That matters most for
result_id, since it is theCREATE TABLE AS SELECTequivalent and the thingthe DDL memo had recorded as missing entirely. It is real, but it is a version further away than this
issue implies, so it should not be scoped as free.
Unrelated, and in the other direction
While enumerating the client for this:
add_managed_table,list_managed_tables,managed_table_layout,create_index,delete_managed_databaseanddelete_managed_tableareall present on the framework client today. So the later phases in
docs/ddl-tools-plan.md(Group A and Group B) are pure wrapping with no upstream dependency at all — which is the opposite
of the situation here, and worth knowing before ordering the phases by risk.
rohan-hotdata commentedon Sep 1, 2026
Follow-up: two amendments to the comment above, and a floor problem it exposes
Cross-checked against a downstream consumer, which was reading 0.10.0 where this repo was reading the
installed 0.12.1. Both readings were right about their own version, and the disagreement is the
useful part.
The closing paragraph above said six methods are "present on the framework client today". That
is true of 0.12.1 and false of
pyproject.toml's declared floor — the same version ambiguity therest of the comment was correcting. Pinning it properly:
add_managed_table,list_managed_tables,create_index,delete_managed_database,delete_managed_tablemanaged_table_layoutload_managed_tableforwardsvar_async/async_after_msformat,result_idfork_databaseSo the "pure wrapping, no upstream dependency" claim holds for five of the six, not all six.
What that means for scoping
managed_table_layoutneeds the floor raised tohotdata-framework>=0.12.0. It is not anupstream ask like
format/result_id— the capability exists — but it cannot be wrapped whilethis package advertises support for 0.10.0. Cheap, and worth doing deliberately rather than
discovering it when someone on the floor version gets an
AttributeError.slightly: at the floor,
load_managed_tableforwards exactlymode,upload_idandkey.fork_databaseis onDatabasesApibut on no framework version. Relevant to the"golden template database" idea, which was already ruled out on other grounds (a fork re-attaches
external catalogs but does not carry indexes over).
The raw-SDK bypass is not hypothetical
Also worth recording from the same cross-check: the
DatabasesApi(client.api).load_database_table(...)route was actually exercised while probing
result_id. The client-scoped path fails withX-Database-Id header is required, and dropping to the raw API is what made the load work. Sowhoever picks up
format/result_idhas a working code path to lift rather than one to discover —though the preference for going through the framework client stands, so the cleaner fix is still a
framework change.
feat: widen the provisioning tools, and mark the ones that can destro…