Skip to content

MILAB-7194: column value references and block dependencies - #1911

Open
AStaroverov wants to merge 30 commits into
mainfrom
MILAB-7194_column-value-refs
Open

AStaroverov wants to merge 30 commits into
mainfrom
MILAB-7194_column-value-refs

Conversation

@AStaroverov

@AStaroverov AStaroverov commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

What

Blocks that pick values of another block's column (conditions, antigens, sample groups) say so through their args, and the platform tracks who uses whose data. Nothing is declared inside a block: references are found structurally in args.

Column value references (pl-model-common, sdk/model, workflow-tengo)

  • ColumnValueId — canonical id of one value of one column: { __isColumnValue: true, column: ColumnUniversalId, value }. The column's leaf names the producer block; template relocation repoints it.
  • collectColumnReferences(args) finds column ids and column value ids anywhere in args, including map keys and stringified JSON.
  • Models build value pickers with readColumnValues(recipe) (cells of a column stored inline as PColumnData/Json, each with its ColumnValueId) and deriveColumnValueOptions(entries) (distinct { value: ColumnValueId, label } options, natural order).
  • Workflows: workflow.column-value with isColumnValueId, valueOf, valuesOf. Blocks unwrap picked values explicitly in wf.body.

Column links (pl-middle-layer, pl-model-common)

  • The middle layer computes each block's column links and puts them in the block state as columnLinks (BlockStateV3); undefined in app versions that do not compute them. Blocks add nothing to their model.
    • dependencies — outputs of other blocks the block's args reference (its current args, else those of its last run);
    • dependents — blocks whose args reference the block's outputs.
  • Each block's links are their own computables: dependencies read the block's args and only the referenced fields of the producers above it; dependents read the args of the blocks below and the block's own last run. A run of one block does not recompute the links of blocks that do not reference it, and the project overview does not read them.
  • Each reference is checked against the producer's last run: ok, pending, notRun, unchecked, broken, missingValues. Picked values are checked against pl7.app/discreteValues, else the column data.
  • The status logic and the alert texts live in pl-model-common/block_links (computeDependencies, computeDependents, linkSections, linkAlerts, describeMissingValue), with their tests.

UI (ui-vue, uikit)

  • PlDropdown, PlDropdownMulti, PlDropdownRef, PlDropdownMultiRef, PlValueLabel and PlDatasetSelector from @platforma-sdk/ui-vue explain a missing pick in a tooltip: the block that was to provide it, as a link, and what went wrong. uikit's versions take the hint as a missingValueHint prop.
  • <PlBlockDependencies /> (no props), optional: alerts for links that need attention — a warning when blocks use data this block does not have or has not produced yet, an error when an output this block uses is unavailable or lacks picked values.
  • PlBlockName and useOpenBlock() open another block of the project; PlatformaV3.openBlock is optional and absent in app versions that cannot do it.

Removed

  • The blockDependencies model plugin, ctx.getOutputDependencies(), ctx.blockId and the middle layer's output dependency index, all added earlier on this branch.

Docs

  • docs/column-identity.md, docs/column-access-api.md, migrations/2026-05-20-new-column-access-mechanism.md (corrections).

Tests

  • pl-model-common (block_links), pl-middle-layer (column_links, offline, including invalidation scope), sdk/model, ui-vue unit tests; tests/drivers-ml-blocks-integration gains a live test that links reach the block state.

Consumers (branches MILAB-7194_column-value-refs)

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new actionable defects were found.

Summary

Adds column-value references and moves dependency checks into block state. Pickers can explain missing selections and link to the block that provides them.

Important touched terms:

  • Column value picks stay tied to the column that supplied them.
  • Each block state shows which columns it uses and who uses its outputs.
  • Block screens explain missing links and let people open the related block.
  • Column access docs spell out selector matching and migration behavior.

Previous-thread context: AStaroverov confirmed that partitioned JSON is intentionally unsupported by these synchronous readers because its parts are files. Greptile accepted that explanation; the current readers preserve that limit.

No new actionable issues were found. Tests and browser checks were not run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Block args"] --> B["Collect column references"]
  B --> C["Check producer's last run"]
  C --> D["BlockColumnLinks"]
  D --> E["Block state"]
  E --> F["Dependency alerts"]
  E --> G["Missing-selection hints"]
  G --> H["Open producer block"]
Loading

Reviews (3) · Last reviewed commit: "MILAB-7194: compute column links in the ..." · Reviewed by Greptile

@notion-workspace

Copy link
Copy Markdown

@changeset-bot

changeset-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7d52947

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 25 packages
Name Type
@milaboratories/pl-model-common Minor
@platforma-sdk/model Minor
@milaboratories/pl-middle-layer Minor
@platforma-sdk/workflow-tengo Minor
@milaboratories/pl-model-middle-layer Minor
@platforma-sdk/ui-vue Minor
@milaboratories/uikit Minor
@milaboratories/pl-crash-recorder Patch
@milaboratories/pf-driver Patch
@milaboratories/pf-spec Patch
@milaboratories/pf-spec-driver Patch
@milaboratories/columns-collection-driver Patch
@milaboratories/pl-client Patch
@milaboratories/pl-drivers Patch
@milaboratories/pl-deployments Patch
@platforma-open/milaboratories.software-ptabler.schema Patch
@platforma-sdk/block-tools Patch
@platforma-sdk/test Minor
@platforma-sdk/pl-cli Patch
@milaboratories/pl-model-backend Patch
@milaboratories/pl-errors Patch
@milaboratories/pl-tree Patch
@platforma-sdk/bootstrap Patch
@milaboratories/ptabler-expression-js Patch
@platforma-sdk/tengo-builder Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.15359% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.59%. Comparing base (43bd891) to head (435eea5).
⚠️ Report is 7 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
sdk/model/src/columns/column_values.ts 81.08% 2 Missing and 5 partials ⚠️
lib/model/common/src/block_links/export_pool.ts 82.85% 1 Missing and 5 partials ⚠️
...b/node/pl-middle-layer/src/middle_layer/project.ts 57.14% 2 Missing and 4 partials ⚠️
lib/node/pl-middle-layer/src/mutator/project.ts 82.35% 4 Missing and 2 partials ⚠️
sdk/model/src/block_model.ts 70.58% 5 Missing ⚠️
lib/model/common/src/block_links/links.ts 95.74% 0 Missing and 4 partials ⚠️
...e/pl-middle-layer/src/middle_layer/column_links.ts 90.32% 2 Missing and 1 partial ⚠️
sdk/model/src/pframe_utils/column_value_options.ts 90.62% 1 Missing and 2 partials ⚠️
...ommon/src/drivers/pframe/spec/column_references.ts 95.45% 0 Missing and 2 partials ⚠️
lib/model/common/src/drivers/pframe/spec/ids.ts 89.47% 0 Missing and 2 partials ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1911      +/-   ##
==========================================
+ Coverage   57.57%   58.59%   +1.02%     
==========================================
  Files         445      458      +13     
  Lines       23457    23994     +537     
  Branches     5288     5478     +190     
==========================================
+ Hits        13505    14059     +554     
+ Misses       8402     8363      -39     
- Partials     1550     1572      +22     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@AStaroverov
AStaroverov marked this pull request as ready for review October 19, 2026 14:21
Comment thread sdk/model/src/columns/column_values.ts Resolved
@AStaroverov AStaroverov changed the title MILAB-7194: column value references, block dependencies and args validation MILAB-7194: column value references and block dependencies Oct 19, 2026
@AStaroverov

Copy link
Copy Markdown
Collaborator Author

@greptileai

@AStaroverov

Copy link
Copy Markdown
Collaborator Author

@greptileai

host: undefined | OpenBlockHost,
): undefined | ((blockId: string) => Promise<void>) {
if (host === undefined || host.apiVersion !== 3) return undefined;
const openBlock = host.openBlock?.bind(host);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a simpler way maybe that doesn't involve .bind()?

…dation

Column value references. `ColumnValueId` names one value of a column a block
picked; template relocation repoints the producer block inside it. In
workflows, `workflow.column-value` reads such ids back into plain values, and
`wf.body` / `wf.prepare` take `{ unwrapValues: true }`. Models build value
pickers with `readColumnValues` / `deriveColumnValueOptions`.

Block dependencies. The middle layer indexes which blocks reference which
outputs from args (`ctx.getOutputDependencies()`, `ctx.blockId`). The
`blockDependencies` plugin and `PlBlockDependencies` show links in both
directions with statuses checked against the producer's last run;
`PlBlockName` and `useOpenBlock` open another block of the project.

Args validation. `.argsValidation(({ args, data }) => ArgsIssue[])` and its
prerun counterpart report run readiness with severity instead of throwing in
args, so incomplete args are still kept as a draft for dependency tracking.
The model runtime has no Intl, so creating a collator at module load broke
every block model bundle. Natural order is now compared by hand.
A workflow turns a picked column value id into its value with valueOf or
valuesOf where it uses it. wf.body, wf.prepare and wf.preparePreRun no
longer take an unwrapValues option, and the deep walk over args that
replaced ids, map keys included, is gone with it.
The args and prerun args derive callbacks return the three states the
middle layer tells apart. Args not ready for no reason given are
notReady; args held back by errors are blocked, with the draft when
args() returned one. A prerun without its own prerunArgs is notReady
while its args are not ready or blocked: an older middle layer skips
staging for that as well, so it needs no error of its own.
A project field created and then removed within one transaction stays on
the backend, pointing at a value the backend has deleted; the next load
of the project fails reading it. Adding a block whose args() throws and
then setting data that makes them valid does exactly that with
currentArgsIssues. The mutator now creates fields the project did not
have when save() runs, so such a field is never sent.
Block dependencies are read from a block's current args, else from the args of its last run;
prerun args are no longer read. This removes argsValidation / prerunArgsValidation, args issues
in the UI, overview and MCP, the draft, issues and staging args fields in the middle layer, and
the argsField runtime API. Args derivation callbacks are back to their { error } / { value }
results.
PlBlockDependencies drops the links panel and its header button: it shows only the alerts for
links that need attention. Blocks using data this block no longer has are a warning, with advice
to add the data to this block or reconfigure those blocks, instead of an error. PlBlockName no
longer takes onOpen, which only closed the panel.
A PColumnData/JsonPartitioned part is a JSON file, not a resource holding `{ data }`, and the
model reads files only asynchronously. readColumnValues gives undefined for such a column, and
picked values in it are unchecked as unreadable, as for binary and parquet data.
PlDropdown and PlDropdownMulti show a selected column value id by the value it
picks: as is while the options load, and as "<value> — not available" when the
options lack it. PlValueLabel shows a value as text the same way, for lists and
other places outside a dropdown. columnValueLabel names a picked value to the
user without reading any data.
A column counts as exported once its spec is in the producer's last run.
Columns marked pl7.app/hideDataFromUi reach block UIs without their data, so
data presence said nothing about the export and such columns, like the
datasets of samples-and-data, were reported as not exported. Picked values
are checked against the pl7.app/discreteValues annotation when the spec has
it, else against the data, read only then.

The dependents output reports hasUnrunChanges, whether the block has changes
its last run does not include. While it does, the PlBlockDependencies warning
says the links are checked against the last run, and that running the block
shows whether the changes fix them.
Dropdowns, ref dropdowns and value labels take missingValueHint, a tooltip
explaining a missing value. The ui-vue exports of these components pass one
when the block has the blockDependencies plugin: the block that was to provide
the column, as a link, and what went wrong. Ref dropdowns show missing values
as "Value not available" like the other dropdowns, and links inside tooltips
use the tooltip link color.
…el plugin

The middle layer computes each block's column links on its own: its dependencies read its args
and only the referenced columns of the blocks above it, its dependents read the args of the
blocks below it and its own last run. They reach the app as `columnLinks` of the block state,
so a run of one block no longer recomputes the links of blocks that do not reference it.

The status logic moves from the blockDependencies plugin to pl-model-common (block_links) with
its tests; the plugin, getOutputDependencies and the output dependency index are removed.
A link to a block that has not been run yet is `notRun`, decided by whether the block has a
last run, and raises both alerts, one line per block.

ui-vue: PlBlockDependencies reads the links from the app and takes no props; the value pickers
and PlDatasetSelector explain a missing selection by them.
@AStaroverov
AStaroverov force-pushed the MILAB-7194_column-value-refs branch from 744ef42 to 0e839dc Compare October 22, 2026 07:13
A value of a number column is picked as its decimal string, so the same value picked from a
number cell and from a text cell is the same ColumnValueId. createColumnValueId refuses a value
that is not a string, and the workflow's valueOf reads back strings only.
…th runIssues

args() and prerunArgs() build args even when the configuration is incomplete, so the middle
layer sees which columns a block references; runIssues() and prerunIssues() say why the block
may not run yet. The middle layer writes the issues found in the current args to the block's
inputsValid field together with a fingerprint of those args, and the project overview lets a
block run only when that record matches its current args and holds no errors. A missing or
foreign record is repaired by deriving the args again when the project is opened or rendered.

The SDK sets the requiresRunIssues feature flag on every block it builds, so an app that would
run incomplete args refuses the block.

A block whose results are up to date no longer keeps its dependents from running when its own
args have run errors: only a block that needs to run and cannot holds them back.
…rame

Value pickers read the picked column's values with getUniqueValues, as blocks did before, so a
column of any storage format can be picked from; reading inline JSON in the model left a column
stored any other way unreadable, and a check over its values open forever.

The model gives createColumnValuesSource(ctx, column) as an output: a frame of the column alone,
resolving while it or its frame is, absent once the column is gone, each with the pick it was
made for. The UI reads it with readColumnValueOptions, or in Vue with useColumnValueOptions,
which follows the pick and tells loading, absent, read or failed. readColumnValues is removed.
runIssues read as issues a run came back with; the issues are found in args, before a run, so
the builder methods are argsIssues and prerunArgsIssues, next to args and prerunArgs. RunIssue
becomes ArgsIssue, the requiresRunIssues flag requiresArgsIssues, and the middle layer's run
errors args errors.
Args without an args errors record of their own - a project saved before the record, or args a
middle layer unaware of it wrote - record no errors and run as before, instead of being derived
again when the project is opened or rendered: such a middle layer refuses every block whose
argsIssues could find errors, so only blocks of earlier SDKs, whose args() throws instead, end up
that way. Opening a project no longer rewrites or re-derives anything.

The rule that an up-to-date block keeps feeding its dependents despite errors in its args holds
only for blocks whose inputsValid comes from their args; a model API v1 block that is not valid,
or a block whose code cannot run here, keeps the blocks below it from running, as before.
…PlColumnValueDropdown

A value picker is made like a data table: the model gives
createColumnValueOptions({ valueColumn, labelColumn? }, { ctx? }) as an output,
a frame of the value column and its label column, and the UI offers it with
PlColumnValueDropdown / PlColumnValueDropdownMulti, or reads it with
usePlColumnValueOptions where the whole list is needed. A label column labels
each value by its row, while the value column's value is what is stored.
Replaces createColumnValuesSource and useColumnValueOptions.
… slots

usePlColumnValueOptions is no longer exported: the pair of
createColumnValueOptions and PlColumnValueDropdown(Multi) is for a dropdown over
the values of a column only, and a block needing the values for anything else
reads its own frame. The dropdowns take exclude, values not offered, and pass
their slots on.
usePlColumnValueSettings({ source, valueColumn }) makes the settings of
PlColumnValueDropdown(Multi) out of the block output and the column picked now:
none, pending until the output is computed for that pick, absent, or present.
It reads no values; the dropdowns take the settings and read the values
themselves, through a reader that is not exported.
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