Repository navigation
feat: add enrich step that records agent-authored insights on the discover inventory - #95
matthewmoorcroft wants to merge 59 commits into
Conversation
Add a source-neutral insights artifact (models/insights.py) and an enrich_inventory pass (discovery_insights.py) that layers agentic insights onto the unified inventory, with a validator that forbids inferred-only keys by presence and aggregates per-edge errors. Expose it through the discover CLI and MCP (enrich subcommand) and document it in the flowx-discover skill. Consolidates PR #82 (agentic insights enrich_inventory + validator + CLI/MCP). Co-authored-by: Isaac <no-reply@databricks.com>
Introduces the first-class flowx-enrich skill (SKILL.md + insights.md, moved here from flowx-discover) and reframes flowx-discover to chain into enrich by default while keeping a deterministic-only skip path. Registers flowx-enrich in the plugin manifest. The agent authors the insights JSON; the library only validates and merges it (no LLM in the library). Skill/manifest docs only; no src/ changes. Co-authored-by: Isaac <no-reply@databricks.com>
Propagate the flowx-enrich skill (from #95) up the stack ahead of adding the routing skill changes on this branch. Co-authored-by: Isaac <no-reply@databricks.com>
FIX 4: The insights validator only accepts edge_type 'control'/'inferred'. Improve the rejection message so a mis-typed cross-pipeline data coupling (e.g. edge_type 'data') is steered onto the 'inferred' tier with the required 'evidence' + 'confidence', instead of just being rejected. Add an explicit "data coupling -> inferred" worked example (wrong vs. right) to the enrich insights.md reference, and strengthen the regression test to assert the guidance. FIX 8a: Port the GA/Preview grounding guidance that previously lived only in the Airflow skill into flowx-enrich. Before recommending a connector or Lakeflow Connect pattern, the authoring agent must verify the feature's current GA-vs-Preview status AND target-workspace availability against public Databricks docs and cite the source; Private Preview is treated as doNotSuggest absent confirmed enrollment. Prose/guidance only. Co-authored-by: Isaac <no-reply@databricks.com>
Propagate the discover loud-0-pipeline warning + activity-count units label up the stack. Co-authored-by: Isaac <no-reply@databricks.com>
Cross-review BLOCKING 2: the FIX 8a prose contradicted itself -- it required verify-before-recommend but then permitted a conditional recommendation when release state is uncertain, and it required citing the source but not recording the resolved status. Fix: remove the conditional-recommendation escape hatch. The agent MUST verify each recommended capability/connector's GA-vs-Preview status AND workspace availability against current public Databricks docs BEFORE recommending it, and MUST record the resolved GA/Preview status (and cite the doc source) in the pattern's fit / conversion_notes. If it cannot verify, it must not recommend the capability and should offer a verified alternative instead. Consistent with the Airflow lakeflow-connect grounding it was ported from. Prose only; references only real insight fields (fit / conversion_notes / recommended_patterns). Co-authored-by: Isaac <no-reply@databricks.com>
…insights Propagate the --adf-source-path warning correction up the stack. Co-authored-by: Isaac <no-reply@databricks.com>
…mended patterns
Turn the enrich GA-grounding rule from prose into a real, validated, structured
field. RecommendedPattern gains optional release_state
("ga"/"public_preview"/"private_preview"/"beta"/"unknown") + release_state_source
(the doc citation). The insights validator now:
- enum-validates release_state when present;
- REQUIRES release_state when simplification_pattern is true (intended new
strictness -- a distinctive capability must declare its verified release state);
- REQUIRES a non-empty release_state_source for public_preview/private_preview/beta
(not for ga/unknown);
- aggregates all violations (no fail-fast), like the rest of the module.
The enrich skill prose now sets the structured fields instead of burying status in
fit/conversion_notes, and documents the tiered surfacing intent: ga = no warning;
public_preview = informational disclosure (production-ready per Databricks, confirm
availability); private_preview & beta = prominent warning; unknown = could not
verify, prefer a verified alternative. The verify-before-recommend /
never-recommend-now-verify-later rule is kept.
Fields are optional/back-compat; only the simplification-requires-release_state rule
is a stricter check.
Co-authored-by: Isaac <no-reply@databricks.com>
Revise the release-state surfacing to pure factual disclosure per design review; the validator/grounding rules are unchanged (release_state still required when simplification_pattern is true; release_state_source still required for public_preview/private_preview/beta). Only how the state is *surfaced* changes: - ga and unknown are silent (unknown is treated exactly like ga); - public_preview is disclosed as "Public Preview (production-ready)"; - private_preview and beta are stated as plain factual labels. Drop the prominent-warning / not-production-ready / gated framing from the skill prose and the RecommendedPattern docstring. The separate recommendation-eligibility rule (treat Private Preview as doNotSuggest without confirmed enrollment) and the verify-before-recommend grounding rule are kept as-is. Co-authored-by: Isaac <no-reply@databricks.com>
Follow the neutral-disclosure design through the RELEASE_STATES constant comment
(state definitions are now purely factual; surfacing is described on
RecommendedPattern.release_state) and the validator docstring ("disclose" rather
than "warn"). No behavior change.
Co-authored-by: Isaac <no-reply@databricks.com>
…rop hardcoded release_state examples Private Preview features are no longer hard-gated as `doNotSuggest`. Instead the flowx-enrich skill now says to disclose the verified release state via the structured release_state / release_state_source fields, note that Private Preview typically needs workspace enrollment/entitlement, and let the customer decide whether to proceed — the tool does not unilaterally refuse to suggest it. The requirement to verify the release state against current public docs before recommending is kept, and so is the neutral (non-warning) surfacing. The release_state mechanism and validator rules are unchanged. Also neutralize the illustrative JSON examples so no real connector is pinned to a literal GA/Preview status (release state goes stale): the system_recommendation and pipeline_insights examples now use anonymized pattern names with a clearly-placeholder release_state value that tells the author to verify live and not copy it. No real-connector-to-status literal remains in the enrich skill. The Airflow-side grounding rule is tracked separately (#103) and is untouched here. Co-authored-by: Isaac <no-reply@databricks.com>
… graph-contract naming) into stack/agentic-insights Co-authored-by: Isaac <no-reply@databricks.com>
… from it Enrich used to merge the insights block straight into inventory.json. It now writes metadata/source_insights.json with the library-owned schema_version, inventory fingerprint, the source_graphs.json hash the inventory records, and its own content hash, then rebuilds inventory.json from discover's deterministic projection plus that document, so the inventory's insights block is always a copy of the file. Enrich refuses an inventory that no longer matches the saved source graphs, and authored insights cannot set the library-owned hashes. merge_into_inventory and the CLI and MCP surfaces keep working as before. Co-authored-by: Isaac <no-reply@databricks.com>
…ights Co-authored-by: Isaac <no-reply@databricks.com>
Wording only. The enrich and discover skills, the enrich CLI help and docstring, the MCP enrich surface and the insights module docstrings still said the library merges insights into inventory.json. They now say it validates them, records them in source_insights.json and rebuilds the inventory, and the insights model docstring uses the source-neutral discovery graph contract name. The enrich example names its authored input authored_insights.json so it is not mistaken for the library's output. Co-authored-by: Isaac <no-reply@databricks.com>
…ntic-insights Carries the #94 review fixes forward: ADF dataset identities qualified by file name and literal server, parameterised parts never form an identity, the shared lineage parser in ir_serde, and the source_graphs.json docs. The flowx-discover output row for source_graphs.json conflicted with this branch's wording commit; the resolution keeps both wordings. Co-authored-by: Isaac <no-reply@databricks.com>
…, fix enrich wording
…/routing-agentic Carries #94, #95 and #96 forward: dataset identity fixes, the shared lineage parser, one shared canonical hash (discovery_serde.canonical_sha256), hardened insights validation and enrich wording, and the agentic component resource-key checks. Conflicts kept both wordings: the AGENTS.md discover line matches phase1-integration, and the MCP tool description takes #95's corrected enrich text with this branch's route and fill_agentic entries. README listed source_insights.json twice after the auto-merge; the two lines are combined into one that keeps both descriptions. Co-authored-by: Isaac <no-reply@databricks.com>
…integration Brings in the final heads of #95 (5fa3ea2), #96 (b508ecc) and #97 (7cc139f), so the integration branch carries every Phase 1 PR as validated: insights hardening and the shared canonical hash, the agentic component checks, and the routing replay model. Co-authored-by: Isaac <no-reply@databricks.com>
…les together Authored insights now carry authored_against, the source_graphs_sha256 the author read from inventory.json. Enrich refuses a missing or different value, so insights written before discover ran again never bind to the newer inventory; inventories without a recorded graph hash need none. source_insights.json and inventory.json are now replaced together: both temp files are written first, and if replacing the inventory fails the previous insights are restored. The enrich insights guide documents the new field. Each fix has a regression test that fails on the previous code. Co-authored-by: Isaac <no-reply@databricks.com>
Fixes the Codex round-5 finding. The edge_identity check accepted a string of only spaces because it tested truthiness, and an inferred edge's identity is never resolved afterwards, so the blank identity was recorded. It is now stripped before the check, as evidence and recommended-pattern text already are. Co-authored-by: Isaac <no-reply@databricks.com>
…agentic-component Co-authored-by: Isaac <no-reply@databricks.com>
…tic-component Co-authored-by: Isaac <no-reply@databricks.com>
… into stack/routing-agentic Conflicts kept both parents' behaviour: - ir_serde: #97's _locate_task split plus #96's placeholder-owned fields for an AgenticComponentActivity replacement. - mcp/server.py, app/README.md: #96's enrich prepare/apply text plus #97's route and fill_agentic entries. - README.md, architecture.mdx: #96's wording. #95's rename reaches #97's own code: the plan, the route audit, routing and their tests now bind to agentic_insights.json and its agentic_insights_sha256 self-hash key (routing read the old key, so after the merge every plan would have recorded a null insights hash). Co-authored-by: Isaac <no-reply@databricks.com>
…nto stack/agentic-insights # Conflicts: # src/flowx/discovery_serde.py # src/flowx/lineage.py
…dges
Addresses the four review points on the ADF discovery lineage:
- A linked service name must be a plain name before it stands in for a
hidden server or account, so an unevaluated [parameters('ls')] never
becomes part of a table identity or a store qualifier.
- The file store reads S3 / GCS bucketName alongside fileSystem and
container, so two buckets with the same folder layout get different
signatures and no longer link.
- build_graph_lineage takes the names of the pipelines in the export, and
an ExecutePipeline edge is resolved only when its child is one of them.
The IR build_control_edges / build_lineage accept the same names.
- A test runs discover twice on the same export and checks the
inventory.json bytes match and carry no generated_at.
Same defect found in the sweep: a call-site folderPath that is an
unevaluated ARM expression was accepted as the weak path signature's
literal anchor; it is now no anchor.
Co-authored-by: Isaac <no-reply@databricks.com>
(cherry picked from commit 1215a8b)
… file names in signatures (cherry picked from commit 69c1762)
…der anchors holding @
Cross-review follow-ups to the review fixes:
- ADF compares pipeline names case-insensitively, so the ADF mapper now
resolves an ExecutePipeline child against the exported pipeline names
that way. The shared control-edge core stays exact-match, and the
edge keeps its target as written.
- The weak path signature rejects an unevaluated ARM expression or a
{{ placeholder as its folder anchor before splitting on @, so a quoted
@ inside an ARM expression no longer slips through. ADF @ expressions
still reduce to their shape.
Co-authored-by: Isaac <no-reply@databricks.com>
(cherry picked from commit 6b0ea5d)
…me spelling (cherry picked from commit 5aa59ec)
…r ARM file names A location that names no store of its own (SFTP, file server, FTP, Azure Files, HDFS, HTTP) now qualifies its path signature with the plain, unparameterised linked service name, and gets no signature when that name is not plain or the linked service takes parameters. An unevaluated ARM fileName holding a quoted @ is kept verbatim instead of being reduced to an ADF expression shape, so two different ARM file names never join. Co-authored-by: Isaac <no-reply@databricks.com> (cherry picked from commit 4f7d2aa)
…ovable stores (cherry picked from commit 9ef2ab0)
Applies the Greg-style review checklist to #95's full diff: - An inventory missing source_graphs_sha256 while source_graphs.json is saved is refused instead of trusted (C1). - The unhashed source and source_dir the rebuild takes from inventory.json must be the graphs' own source and a string (C4, G1). - Only string pipeline names and string control-edge fields count, and only edges discover marked resolved can be annotated (B4, B1). - Malformed pipelines, lineage or control_edges are treated as unknown rather than crashing (D1). - The enrich skill states that one-shot MCP migrate records no insights and that only resolved control edges can be annotated (E4). Co-authored-by: Isaac <no-reply@databricks.com>
# Conflicts: # src/flowx/sources/adf/dataset_lineage.py # tests/unit/test_adf_dataset_lineage.py # tests/unit/test_adf_discovery_mapping.py # tests/unit/test_adf_inventory_superset.py
Co-authored-by: Isaac <no-reply@databricks.com>
ghanse
left a comment
There was a problem hiding this comment.
The enrich design matches the description, and the validation, hash binding and rollback are well tested. Fix the false success on a truncated inventory before merging.
| return {"ok": False, "error": f"No inventory.json under {inventory_path.parent}; run discover first."} | ||
| with tempfile.TemporaryDirectory(prefix="flowx-inventory-") as temporary: | ||
| shutil.copy(inventory_path, temporary) | ||
| return {"ok": True, "output_dir": str(output_dir), **_bundle_output(p, Path(temporary))} |
There was a problem hiding this comment.
Blocking: prepare returns ok: True even when inventory.json was dropped by the 2MB size cap, so the agent gets success with nothing to read. Fail when inventory.json is missing from the bundle (point to the output path instead), and add a test with an oversized inventory.
| @contextmanager | ||
| def _enrich_lock(metadata_dir: Path) -> Iterator[bool]: | ||
| """Hold the output directory's enrich lock, yielding ``False`` when another enrich already holds it. | ||
|
|
||
| The lock is a file created with ``O_EXCL`` (portable, unlike ``fcntl``) and removed when the | ||
| enrich finishes or raises. It is left behind only when the two files may disagree -- a killed | ||
| process, or an error that is or was raised from :class:`_UnrecoveredWriteError` (a failed | ||
| rollback, or an interrupt between the two replaces) -- so the next enrich refuses until the lock | ||
| is removed. | ||
| """ | ||
| lock_path = metadata_dir / ENRICH_LOCK_FILENAME | ||
| try: | ||
| os.close(os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)) | ||
| except FileExistsError: | ||
| yield False | ||
| return | ||
| keep_lock = False | ||
| try: | ||
| yield True | ||
| except BaseException as error: | ||
| keep_lock = isinstance(error, _UnrecoveredWriteError) or isinstance(error.__cause__, _UnrecoveredWriteError) | ||
| raise | ||
| finally: | ||
| if not keep_lock: | ||
| lock_path.unlink(missing_ok=True) |
There was a problem hiding this comment.
This lock only stops two enrich runs from overlapping. discover removes metadata/ (and the lock with it), so a discover during an enrich can let the old insights overwrite the new inventory. Sequential use is fine. The risk is the recovery path that re-runs discover.
…nning enrich Greg's round-3 review of #95: - MCP enrich prepare returned ok: true when inventory.json was dropped by the inline size bound. It now fails and points to output_volume_path / output_workspace_path. - Discover cleared metadata/ (and the enrich lock with it) under a running enrich, so that enrich's old insights could overwrite the new inventory. The lock now records its holder's process id, and discover refuses while that enrich is still running. A lock left by a killed enrich is still cleared by discover, so the hosted recovery path is unchanged. Co-authored-by: Isaac <no-reply@databricks.com>
Replaces the process-id check from 750037e (option B of the round-3 lock decision). A PID cannot prove ownership after PID reuse, and a check in discover still raced an enrich that started just after it. - Discover is unchanged again and still clears any lock, so the hosted recovery for a stale lock is untouched and Airflow needs no change. - The lock holds a random token. Right before writing, enrich checks that its own lock is still in place and that inventory.json still holds the bytes it read; otherwise it writes nothing and reports "discover ran during this enrich; prepare and enrich again". The check runs before the temp files are written and again right before the first replace. - An enrich only removes the lock if it is still its own. - enrich prepare reports the size of the copied inventory, not the live file. Co-authored-by: Isaac <no-reply@databricks.com>
Option A of the round-3 residual-race decision: sequential use is safe and a discover during an enrich is detected around the read and before the write; the handoff windows between the final check and the replaces, and in lock release, are documented as not closed. Co-authored-by: Isaac <no-reply@databricks.com>
A move only: every function and constant keeps its exact body (checked by comparing the AST of each definition before and after). dataset_identity holds the physical identity and the value helpers it shares, dataset_store the store that qualifies a signature, path_signature the structural signature, and dataset_lineage puts them together for each read and write. activity_data_assets and resolve_dataset_identity are still imported from dataset_lineage, the only names #95 to #97 use. Co-authored-by: Isaac <no-reply@databricks.com>
|
Thanks Greg, both round-3 comments are addressed.
|
Co-authored-by: Isaac <no-reply@databricks.com>
Intent
Add an enrich step to discovery, run after discover. An agent authors insights about the factory. Enrich checks them against the inventory and records them in
metadata/agentic_insights.json. The library then rebuildsinventory.jsonby itself, fromsource_graphs.jsonplus that document under one addedinsightskey, so a later step reads the source graph and the enrichment from one file. flowx itself runs no LLM: the agent authors the insights and passes nothing else, and the library validates them, records them and builds the inventory.What changed
models/insights.py,discovery_insights.py: the insights schema and its validator.controlrelationship may name a pipeline discover left unlisted (filtered out with--pipeline, or with no activities) when its edge is a resolved control edge in the inventory.controlrelationship must match a real(from, to, via_task_key)lineage edge markedresolved: true. Aninferredrelationship must carry evidence and a confidence level.release_state.authored_against(thesource_graphs_sha256the agent read) is optional. When given it must match, so insights written before discover ran again are refused; when absent, the library stamps the hash itself. The skill passes it by default.source_graphs_sha256, the savedsource_graphs.jsonmust still be a usable object matching that hash, its source must equal the inventory'ssource, andsource_dirmust be a string. Withsource_graphs.jsonsaved, an inventory missing its hash is refused.agentic_insights.jsonis stamped with the schema version and with inventory, source-graph and insights (agentic_insights_sha256) hashes.authored_againstis checked but not stored.source_graphs.jsonwith the sharedbuild_source_inventory(project_inventory). Onlysource_dirand the hash come frominventory.json, plus a per-source rule for listing empty pipelines. The result is byte-identical to what discover writes, and the insights are validated against it. Inventories without a graphs hash (Airflow today) keep reusing their existing deterministic part.discovery_serde.canonical_sha256is now public so the two modules share one hash.metadata/.enrich.lockfrom reading the inventory until both files are replaced, and a second call made meanwhile gets a violation and writes nothing.metadata/, the lock with it. So before writing its temp files and again right before the replaces, enrich checks that its own lock and theinventory.jsonbytes it read are unchanged; if discover ran, it writes nothing and reports "discover ran during this enrich; prepare and enrich again".agentic_insights.jsonone write ahead ofinventory.json. The lock then stays behind, so the next enrich refuses. Locally, deletemetadata/.enrich.lockand run enrich again; on the hosted MCP server, re-run discover (which clearsmetadata/and the lock), then prepare and enrich again. Second, a discover plus two or more enrich runs on one output folder within a few milliseconds can slip past the checks; because the temp file names are shared, one such case can leave a staleinventory.jsonwhile enrich reportsok: true. Sequential use is safe. Both gaps are stated in the module docstring and the skill, and per-run temp names are a tracked follow-up.enrichsubcommand (--output-dir,--insights-path,--out) and the MCPenrichcommand. On MCP,action: "prepare"returnsmetadata/inventory.jsonfor a hosted agent that can't read the server'soutput_dir. It goes through the same size-bounded inline or volume/workspace deliverypackageuses for bundles. An inventory too large to return inline makespreparefail withok: false, naming the upload options. The defaultaction: "apply"records insights given inline or as a path.flowx-enrichskill (SKILL.md, plusinsights.mdfor the schema). It is registered inplugin.json, the README and the install docs.flowx-discovercontinues into it unless a deterministic-only run was requested; the one-shot MCPmigratecommand runs without it.337d2d6, which includes refactor: add source-neutral discovery graph contract and shared lineage core #84's final head, is merged in (da8f83a, thena45bb58); the conflicts were all in feat(adf): save source graphs with dataset lineage at ADF discover #94-owned ADF files and were resolved to feat(adf): save source graphs with dataset lineage at ADF discover #94's side. That history includes880c202and its revert8e7d302, souv.lockis unchanged.How it was tested
mcpextra, which CI does not install), ruff and mypy pass.337d2d6, and Airflow output is unchanged. On those fixtures, and on an export with an empty pipeline, the inventory rebuilt fromsource_graphs.jsonis byte-identical to discover's, and enrich adds only theinsightskey.authored_against), and a failed write could leave the two files disagreeing (now a paired write with rollback).278b57e, with8fa9bacfrom the no-mistakes review:source_graphs.jsonthat is not a JSON object crashed enrich. It is now a "not usable" violation. The review also found that malformed graph entries in an unhashed file still crashed enrich, so the recorded hash is now compared before any graph is read.3a8dd03: a failed inventory replace rolled the insights file back by truncating it in place, so a second disk error left it partial and the lock was dropped. It is now restored by atomic replace, and the lock is kept if the rollback fails. The no-mistakes review then found that a Ctrl-C landing just after a successful rename was rolled back as if the rename had failed;fb9b933rolls back only onOSErrorand keeps the lock for an interrupt between the two replaces.5d86088), and a hosted MCP agent had no way to read the inventory it authors against (ffe7229, theprepareaction, narrowed toinventory.jsonin7494460so large ARM files can't crowd it out of the size bound).2fb4355: a whitespace-onlyedge_identitypassed validation; it is now stripped before the non-empty check.3f4c456: an inventory with its hash deleted was trusted, the unhashedsourceandsource_dirdrove the rebuild, numeric names were stringified into valid names, an unresolved control edge could be annotated, malformed inventory shapes crashed enrich, and the skill did not say the one-shotmigrateskips enrich. The no-mistakes review then found that a resolved control edge into an unlisted pipeline could not be annotated, although the skill says it can; fixed ina354544.preparereported success when the inventory was dropped by the inline size bound (750037e,8951cc3), and a discover during an enrich could let old insights overwrite the new inventory (8951cc3; the remaining windows are documented in117260a,0fc77bfand6ed0b4b). A process-id check in discover was tried first and replaced, because it could not tell a stale lock from a live one.da8f83a, with the same checklist, found nothing: the resolution keeps every refactor: add source-neutral discovery graph contract and shared lineage core #84 and feat(adf): save source graphs with dataset lineage at ADF discover #94 change, and enrich agrees with the stricter carried rules, including the byte-identical rebuild.9a0ae6b: the rebuild fromsource_graphs.json, optionalauthored_against, and the rename fromsource_insights.jsontoagentic_insights.json. Each has a test that fails without it.Merge order
Merge #84 first, then #94 retargeted to
main, then this PR (retargeted tomainonce #94 has merged), then #96 and #97.The enrich and discover docs point to the
flowx-routeskill, which lands with #97.This pull request and its description were written by Isaac.