feat(ontology): add coded actions for writes SQL cannot express [DS-9390] - #2987
feat(ontology): add coded actions for writes SQL cannot express [DS-9390]#2987nathanverghese-uipath wants to merge 34 commits into
Conversation
The skills said a federated class was read-only and could not be targeted by a write action. That is wrong: reads and writes both traverse FQS, which resolves the external connection and routes the statement to the source system. The modeler's own reference guides already said actions work on both native and federated entities, so the branch contradicted itself. Corrects authoring's federated rules, the modeler's standalone-mode paragraph, and the mapping guide's restrictions block. `readOnly` survives in CLASS_MAP as an explicit per-source exception the author states, never inferred from federation. The source system stays the authority on whether a write succeeds, and a rejection surfaces as an upstream error on the failing step. Left alone: a federated entity still cannot be created via CLI or API, which is provisioning rather than data access. skills/uipath-platform/references/ data-fabric/ still states federated rows are read-only; that is the `uip df records` surface rather than the FQS path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…idator Validates a coded-action TTL against the job that implements it, with no backend calls. JSON output shaped like ontology_preflight.py. Nine gates: marker resolution by name against params and ont:bindsTo, input equality with the marker, per-edit writes attribution, fields existing in the local .ofn, contract strictness, PENDING_DEPLOY state, and an optional typecheck reported as skipped when npx is absent. The gate that matters is writes attribution. ont:writes is the union over every branch the job could take, not a prediction of one run; an edit touching any field outside it is refused whole at `Preparing write statement` after the job has already run. An unrecognised property shape routes to unresolved and fails the gate rather than reading as no writes, so absence never passes as success. Tests use real subprocess and real files, no mocks. Two assert the fail-closed posture directly: an absent schema skips the field gate rather than passing it, and untraceable edit properties fail rather than reading as no writes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The guide mirrors the declarative action guide's shape: semantic questions, a deterministic structured table, then the TTL template. The job contract is stated language-agnostically (input mirrors the marker, rows arrive with physical column names, edits go out in logical names, no network, no credentials) with the concrete template in a per-language section. TypeScript is the only entry today; a further language adds a sibling section rather than a new guide. coded-action-example.md carries tagOverdueTicket as the full worked pair and flagBigOrder as the shape variant: a per-row loop that reads one entity and writes another. SKILL.md gains the jobs artifact row, the guide in its reference list, gate 8, and `kind: SQL | CODED` plus the coded fields on the delegated OPERATIONS handoff. CODED maps to the wire value ont:language "IMPERATIVE", which the backend parser matches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Takes generated jobs and coded-action TTLs and returns TTLs naming a real
Orchestrator folder. Six phases: scaffold a {name}-jobs Solution, stage each
job into its project, pack and publish and deploy, resolve the numeric folder
id, patch it over the PENDING_DEPLOY placeholder, hand the paths back. This
skill never calls `uip ont`, mirroring the modeler's never-upload rule.
Three Python scripts, dry-run by default with --execute gating every mutating
path. Org and tenant come from `uip login status`, never from a parameter, and
nothing about any environment is baked in.
References carry what the packaging spike established against a live tenant: a
CLI-scaffolded Solution does yield a real job release (ProcessType Function),
publishing is async before deploy, a new version means a new deployment in a
new folder under Shared, and republishing an existing version is a silent
no-op everywhere. failure-signatures.md pairs each symptom with the command
that distinguishes it from its look-alike, including the three that read as
success: a publish that changed nothing, a guard refusal that looks like a
no-op, and an `Unexpected error` with zero jobs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 1's write-op scan now classifies each operation on a four-test rubric, first match wins: the value must be computed from stored data or the clock or string construction; the operation reads before writing, branches, loops over rows, writes several rows, or may write nothing; a rule depends on a fact the caller must not be trusted to assert. Otherwise SQL. Ambiguous cases default to SQL, so the declarative surface stays the norm and a job is the exception. Adds the sequencing step between the modeler's return and the Tier 2 upload: when coded actions exist, delegate to uipath-ontology-coded-action-deploy, then re-run the coded preflight on the patched TTLs before uploading. Coded TTLs are held out of Tier 2 until patched. A SQL-only inventory skips the leg entirely. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`type<T>()` is inert at runtime, so the JSON Schema the platform validates against has to be derived from the job's interfaces. Studio Web's packer does that; `uip functions pack` cannot, and refuses outright on the idiom. Deriving it here keeps the interfaces as the single source of truth and keeps the pipeline on `uip solution pack`, which only zips a directory and reads no TypeScript. The lowering reproduces byte-for-byte the manifests Studio Web produced for the two jobs that deployed and ran on a live tenant. Both are committed as goldens, because they are the only evidence of what the platform accepts. An interface's `[key: string]: unknown` lowers to a permissive additionalProperties, which read rows need since SELECT * carries columns the job never declared; its absence lowers to additionalProperties:false, which faults a drifted input before the handler runs. A contract outside the lowerable grammar is refused rather than approximated: a manifest that disagrees with the interfaces faults the job at invoke time, with nothing written and no user log line to explain it. The preflight `input-strictness` gate now runs the deriver and inspects what comes out, instead of passing a type<T>() contract with a note claiming the SDK supplies the flag. The gate checks the same property for both idioms, and a zod contract still needs .strict() on its top-level object. 13 tests for the deriver, at tests/scripts/ so CI picks them up: the golden comparison, six fail-closed cases, drift detection, and uniqueId preservation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…manifest `uip functions pack` was in the pipeline for one reason: it generates the entry-points.json that `uip solution pack` requires and never produces. It also cannot lower the type<T>() contract idiom on any SDK version, which is what forced generated jobs onto a different contract idiom than the verified ones. Stage now derives the manifest with tools/entry_points.py and pack no longer runs `uip functions pack`. This is what the working pipeline always did: it ran solution pack, publish and deploy over a tree whose manifest was already derived, and never invoked the functions packer at all. Jobs are staged as the project's root main.ts, the layout the verified Studio Web export shipped, matching uipath.json's `main: main.ts:default` and the manifest's `filePath: content/main.ts`. Those three have to agree; the scaffold writes the first two and stage writes the third. Consequences: nothing in stage, pack, publish or deploy runs an installer, so GH_NPM_REGISTRY_TOKEN drops to a scaffold-only concern. The SDK is declared as a devDependency for local typechecking rather than a runtime dependency, since type<T>() is erased at compile time and defineFunction comes from the runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generation emits plain interfaces behind the SDK's type<T>() marker, which is what the verified jobs use and what the deploy step derives the manifest from. The guide states the constraint that replaces the old strictness rule: the interfaces must stay inside the grammar the deriver can lower, and an index signature on a row interface is what makes the extra columns SELECT * returns legal while the input object stays closed. Both worked examples now carry the verified sources verbatim rather than a transliteration. zod is documented as recognized when found in an existing source, since such a contract carries its own schema, but not as what generation produces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @nathanverghese-uipath's task in 3m 7s —— View job PR Review: feat(ontology): add coded actions for writes SQL cannot express
SummaryAdds a new skill Change-by-Change Review1.
|
| Area | Status | Notes |
|---|---|---|
| Frontmatter | OK | Valid YAML, name matches folder, description under 1024 chars. Missing → redirects in description — Low |
| E2E Tests | Issue | No task YAMLs for the new skill; preflight tests misplaced outside CI path — Medium |
| Skill Body | OK | Well-structured, prescriptive, complete phase documentation |
| References & Assets | OK | Clean separation of concerns, no duplication, all links resolve |
| Repo Hygiene | Issue | No CODEOWNERS entries, no skill-status.json entries for any ontology skill — High |
Issues for Manual Review
- CLI command correctness: The
uip solution deploy runflags,uip or processes get --all-fieldsoutput shape, anduip solution publish --waitbehavior are asserted based on verified live runs documented in the PR. The reviewer should confirm these match current CLI behavior. - Federated entity write support: The PR corrects the previous "federated = read-only" stance. The
uipath-platformskill's Data Fabric references still say federated rows are read-only (as the PR description notes). The platform skill owners should confirm whether this holds foruip df recordsvs the FQS path. - The
ont:writesas repeated triples vs. RDF list: The PR asserts the runtime silently ignores writes declared as a list. This is a critical semantic difference that should be confirmed by the platform team.
Conclusion
Request changes — two items must be resolved before merge:
- Add CODEOWNERS entries for all
uipath-ontology-*skill paths and their test paths. - Add
assets/skill-status.jsonentries for all three ontology skills (likelyin-development).
The known code bugs (documented in the PR description) are acceptable for a feature branch targeting feat/ontology-onboarding rather than main, but the deployments() fail-open and the solution_name() dispatch issue should be tracked for resolution before the feature branch merges to main. The misplaced preflight tests should be moved to tests/scripts/ to get CI coverage.
|
Consider adding smoke tests before merging. |
…nd guard it The point of one script per action is that determinism comes from the scripts and context from the skill. That only holds if the two cannot drift: a SKILL.md table naming a script that no longer exists, or calling a mutating script read-only, is worse than no table, because an agent trusts it. So the scripts are the source of truth. Each answers --describe with its name, purpose, phase, inputs, outputs, whether it mutates, and its exit codes. SKILL.md's Scripts table refers to them, and each phase section now points at its script instead of restating flags. tests/scripts/test_coded_action_scripts.py makes the prose answer to the code. It asserts every entry point describes itself with the required keys, that the table lists exactly the entry points, that table and script agree on phase and on what mutates, that --describe works on a script with required positionals without performing the action, that no doc still names a script that no longer exists, and that the underscore-prefixed modules are not entry points. Verified the guard actually guards: it fails when the table calls publish_package read-only, and when a script is dropped from the table. It also caught two prose mentions of solution_release.py I had missed in pipeline.md and failure-signatures.md. Wired into CI, which none of this ran in before. test-helpers.yml triggers on tests/scripts/** but every job named specific files, so neither the validator suite nor the deriver suite was ever executed there -- the golden payload was guarding nothing. One new job runs all three, and the trigger paths now include tools/**, tests/coded_action_preflight/** and the deploy skill, so a change to the code reaches its own guards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…instead of inheriting one write_entry_points fell back to "keep whatever manifest is already in the project" when a contract could not be lowered. That is wrong in the one case it fires. The template skeleton shipped a manifest carrying the exemplar's input schema, so an unlowerable job inherited tagOverdueTicket's contract and deployed under a schema that had nothing to do with it: pack succeeded, deploy succeeded, and the job faulted at invoke time on additionalProperties for fields it never declared. A schema not derived from this job cannot be attributed to it, so there is nothing safe to keep. Staging now dies with the reason and what to do about it. Only the entry point's identity carries over -- uniqueId, which the project's bindings reference. The skeleton's input/output schemas are stripped too, so there is nothing to inherit even if the fallback were reintroduced. Deleting zod narrowed this exposure but did not close it: a type<T>() job with a Date field reaches the same path. Verified both directions: a job with an unlowerable field is refused at stage with "cannot lower type 'Date'", and a good job still stages with a derived manifest. Docs brought in line: the contract guide's zod section becomes "the idiom this pipeline refuses" and says why hand-writing a manifest is worse than the refusal; tools/README describes one idiom and the new module layout; NOTES.md records that the skeleton's schemas are absent on purpose, and that the SDK is a devDependency rather than a zod dependency no code adds any more. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… example patch_action_ttl.py's usage line carried 3225065, a live folder id from the sandbox tenant, and still named the script ttl_patch.py. Both wrong in one line: a placeholder now, under the script's own name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
There are confirmed fail-open/idempotence and parsing issues (deploy list failure handling and repeated-predicate extraction) that can lead to incorrect deployments or incomplete validation.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends the ontology skill family to support coded actions (imperative jobs for writes that SQL cannot express), introducing a shared contract deriver/validator under tools/, adding a new deploy-focused skill (uipath-ontology-coded-action-deploy) for the Orchestrator leg, and wiring in CI guards for the new tooling.
Changes:
- Add
tools/entry_points.pyto derive Functionsentry-points.jsonfromtype<T>()interface contracts, with golden tests. - Add
tools/coded_action_preflight.pyand thetools/coded_action/package to validate TTL+job coded-action pairs, with a full-payload golden test suite. - Add the new
skills/uipath-ontology-coded-action-deployskill + scripts, and integrate coded actions into authoring/modeler docs and CI.
File summaries
| File | Description |
|---|---|
| tools/README.md | Documents coded-action preflight and entry-points deriver utilities. |
| tools/entry_points.py | Adds contract→manifest deriver for Functions entry-points manifests. |
| tools/coded_action/init.py | Defines coded-action validation package scope and guarantees. |
| tools/coded_action/action_model.py | Parses coded-action TTLs into an actionable model. |
| tools/coded_action/contract.py | Loads and uses the shared entry-points deriver. |
| tools/coded_action/gates.py | Implements coded-action validation gates. |
| tools/coded_action/job_source.py | Scans TS source to infer written edit properties. |
| tools/coded_action/pairs.py | Discovers TTL/job pairs and schema term sets. |
| tools/coded_action/turtle.py | Adds Turtle lexing helpers used by the validator. |
| tools/coded_action/typecheck.py | Adds optional TS typecheck against an SDK stub. |
| tools/coded_action/verdict.py | Defines gate ordering and JSON verdict shape. |
| tools/coded_action_preflight.py | Adds the coded-action preflight CLI that runs all gates. |
| tests/tasks/uipath-ontology-modeler/_shared/fixtures/entry-points/README.md | Explains entry-points golden fixture provenance and assertions. |
| tests/tasks/uipath-ontology-modeler/_shared/fixtures/entry-points/tagOverdueTicket.ts | Golden job fixture for manifest derivation. |
| tests/tasks/uipath-ontology-modeler/_shared/fixtures/entry-points/tagOverdueTicket.golden.json | Golden manifest fixture for tagOverdueTicket job. |
| tests/tasks/uipath-ontology-modeler/_shared/fixtures/entry-points/flagBigOrder.ts | Golden job fixture for manifest derivation. |
| tests/tasks/uipath-ontology-modeler/_shared/fixtures/entry-points/flagBigOrder.golden.json | Golden manifest fixture for flagBigOrder job. |
| tests/scripts/test_entry_points.py | Adds tests asserting derivation matches Studio Web goldens + fail-closed behavior. |
| tests/scripts/test_coded_action_scripts.py | Ensures deploy scripts’ --describe contracts match SKILL.md and docs. |
| tests/coded_action_preflight/test_coded_action_preflight.py | Adds behavioral tests + targeted per-gate mutations + full payload golden. |
| tests/coded_action_preflight/golden/support.json | Pins full preflight JSON output shape/order for a known-good pair. |
| tests/coded_action_preflight/fixtures/support/support.ofn | Schema fixture for coded-action preflight tests. |
| tests/coded_action_preflight/fixtures/support/support-tagOverdueTicket.ttl | TTL fixture for coded-action preflight tests. |
| tests/coded_action_preflight/fixtures/support/jobs/tagOverdueTicket.ts | Job fixture for coded-action preflight tests. |
| tests/coded_action_preflight/fixtures/support-zod/support.ofn | Schema fixture for “standard schema contract refused” tests. |
| tests/coded_action_preflight/fixtures/support-zod/support-tagOverdueTicket.ttl | TTL fixture for “standard schema contract refused” tests. |
| tests/coded_action_preflight/fixtures/support-zod/jobs/tagOverdueTicket.ts | Zod-contract job fixture to ensure failure at input-strictness. |
| skills/uipath-ontology-modeler/SKILL.md | Updates modeler contract/docs for coded actions and federated write semantics. |
| skills/uipath-ontology-modeler/references/mapping-yarrrml-guide.md | Corrects federated-class write guidance and READONLY handling. |
| skills/uipath-ontology-modeler/references/coded-action-example.md | Adds worked coded-action examples and rationale. |
| skills/uipath-ontology-coded-action-deploy/SKILL.md | Introduces new deploy skill and end-to-end pipeline guidance. |
| skills/uipath-ontology-coded-action-deploy/scripts/stage_jobs.py | Adds phase-2 staging entrypoint (temp-only) with --describe contract. |
| skills/uipath-ontology-coded-action-deploy/scripts/resolve_folder_id.py | Adds folder path→numeric id resolver script with --describe contract. |
| skills/uipath-ontology-coded-action-deploy/scripts/publish_package.py | Adds guarded publish step (dry-run default) with --describe contract. |
| skills/uipath-ontology-coded-action-deploy/scripts/patch_action_ttl.py | Adds guarded TTL patcher (dry-run default) with optional process URL insertion. |
| skills/uipath-ontology-coded-action-deploy/scripts/next_version.py | Adds live deployment-based version bump helper. |
| skills/uipath-ontology-coded-action-deploy/scripts/deploy_release.py | Adds guarded deploy step with idempotence checks and folder invariants. |
| skills/uipath-ontology-coded-action-deploy/scripts/build_package.py | Adds local-only pack step wrapper. |
| skills/uipath-ontology-coded-action-deploy/scripts/await_release.py | Adds release readiness poller with explicit states. |
| skills/uipath-ontology-coded-action-deploy/scripts/_uip.py | Centralizes CLI JSON/plain execution and stdout/stderr contract. |
| skills/uipath-ontology-coded-action-deploy/scripts/_staging.py | Implements staging tree creation + manifest derivation + pack wrapper. |
| skills/uipath-ontology-coded-action-deploy/scripts/_solution.py | Reads solution/deployment state on disk and from tenant. |
| skills/uipath-ontology-coded-action-deploy/references/pipeline.md | Documents the pipeline and required value shapes. |
| skills/uipath-ontology-coded-action-deploy/references/failure-signatures.md | Documents misleading failure modes and discriminating checks. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/TagOverdueTicketProcess/uipath.json | Provides template project metadata/function map. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/TagOverdueTicketProcess/tsconfig.json | Provides template TS compiler settings for function projects. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/TagOverdueTicketProcess/project.uiproj | Provides template project type metadata. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/TagOverdueTicketProcess/package.json | Provides template devDependencies for local typechecking. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/TagOverdueTicketProcess/entry-points.json | Provides template entry-point envelope/identity to be rewritten by staging. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/TagOverdueTicketProcess/bindings_v2.json | Provides template bindings referencing entry-point uniqueId. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/SolutionStorage.json | Provides template solution storage manifest. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/Solution.uipx | Provides template solution manifest metadata. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/resources/solution_folder/process/function/TagOverdueTicketProcess.json | Provides template Orchestrator function process resource descriptor. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/resources/solution_folder/package/TagOverdueTicketProcess.json | Provides template Orchestrator package resource descriptor. |
| skills/uipath-ontology-coded-action-deploy/assets/solution-skeleton/jobs.map.json | Provides template job→project mapping file. |
| skills/uipath-ontology-coded-action-deploy/assets/NOTES.md | Documents how template instantiation renames/regenerates skeleton artifacts. |
| skills/uipath-ontology-authoring/SKILL.md | Adds coded-action classification, delegation sequencing, and federated write corrections. |
| .github/workflows/test-helpers.yml | Wires CI to run the new validator/deriver/script-contract tests on relevant changes. |
Review details
- Files reviewed: 60/60 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
All four were confirmed by reproduction before changing anything, and all four now have a regression test; the repeated-predicate one was verified to fail against the old code. **Repeated ont:writes predicates were under-reported.** `ont:writes "A", "B" ;` and the same predicate written twice are equivalent Turtle, but quoted_objects used re.search and read only the first occurrence. The writes gate then blamed the JOB for an edit the TTL did in fact cover -- a false failure pointing at the wrong file. Now every occurrence, in order. **deployments() failed open.** `allow_fail=True` turned a failed `uip solution deploy list` into an empty list, which the idempotence guards read as "nothing is deployed". A transient CLI or API error would have read as "safe to create" and added a second deployment and a second Orchestrator folder for a version that already had one. It now dies with the CLI's own failure. **manifest() inherited a stale filePath.** It preserved filePath from an existing manifest as well as uniqueId, so the caller's value never won on a rewrite. A manifest from the earlier `functions/<job>.ts` layout would have kept pointing at a file the package no longer contains. Only uniqueId is identity; filePath names what was just staged. **Two error strings named scripts that no longer exist** -- `solution_scaffold.py` in a die() message and in scaffold_solution.py's own usage line. The docs sweep missed them because they are inside Python strings. Reported by Copilot on the PR. The first two were also in the Known gaps table of the PR description; that table shrinks accordingly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four points from Vikas on the PR. **Do not classify on "business rule" or "constraint".** Neither term is defined yet, and the boundary they draw is the wrong one. The rubric now leads with the question that actually decides it: does producing the edit need computation the write surface cannot express. Anything reaching past a single statement of literal values belongs on the coded leg, whether or not anyone would call it a business rule. Stated explicitly: the tests would not move if the write surface grew procedural extensions, because what the coded leg buys is a Turing-complete language rather than more SQL grammar. The routing bullet is reworded the same way. **Refuse an operation that reads after it writes.** A coded action's declared reads all run before the job and its edits all apply after it returns, so no job can observe its own writes or interleave a read between two of them. An SDD asking for that describes a sequence this shape cannot express; the authoring gate now stops and names the operation and the ordering, rather than generating a job that reads pre-write state and looks correct. **"optional, rare" said nothing.** `readOnly` is now just a CLASS_MAP field, and what it means is stated where the federated rules are: an exception the author states for a source that rejects writes, never inferred, so most CLASS_MAPs carry it on no class at all. **Say where the worked examples are.** tagOverdueTicket and flagBigOrder are real and worked end to end in the modeler's coded-action-example.md; the rest of the table is named for shape only, and the verdict follows from the rubric. Two pairs is deliberate: more would be volume without a new shape. Also, per the Copilot review, the new skill's frontmatter description gains the compact sibling redirects the other skills carry. Deferred by request: the wire value `ont:language "IMPERATIVE"` becoming "CODED", which is also what Vikas's "the two options are not logically comparable" comment is about. That change lands with the ontology service. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…walkable Two CI gates were failing, both surfacing conditions older than this PR. **No ontology skill was in assets/skill-status.json.** Not the new one, and not uipath-ontologies, -authoring or -modeler either; the check only ran once this PR touched two of them. All four are registered as in-development, which is what they are while the ontology service is unmerged, and the README status table is regenerated from the manifest. **The verb gate rejected every `uip ont` reference.** It blocks on a Stale finding where no catalog prefix matched at all, and `ont` is in no catalog: the group registers only when the CLI version carries a prerelease tag, so a catalog built from a released CLI never sees it. 58 blocking findings across the four skills, and the same fate waiting for anyone who next touched uipath-ontologies. Rather than scatter 28 per-line skip markers through commands people copy, `ont` joins `rpa` in the builder's PLATFORM_SPECIFIC_PREFIXES. That set already exists for exactly this shape -- a group absent from the machine that builds the catalog -- and it marks a prefix unwalkable only when the current build does not expose it, so a catalog built from a prerelease CLI still resolves `uip ont ...` concretely. References under it become Uncertain, which is the honest severity: not wrong, unverifiable from here. Added to the snapshot too, so it takes effect before the next nightly refresh rather than after it. Verified before making the change that all 58 blocking findings were the ontology family and none were elsewhere, so re-gating every skill -- which touching the snapshot triggers -- introduces no new failure. Repo-wide blocking count is now 0; the 509 soft-stale findings are warnings the gate does not fail on and are untouched. Two real content fixes fell out. uipath-ontologies deliberately shows `uip onto` and `uip ontologies` as wrong prefixes, so those four lines carry the sanctioned skip marker. And pipeline.md's value table had a row labelled "uip binary", which parses as a verb; it is a column label, so it reads "CLI binary" now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nnot make it safe
827 lines out: 484 of deploy scripts, 261 of skeleton assets, 82 of tools. Nine
entry points become six, and phase 1 becomes four documented `uip` commands.
The line drawn per script: code earns its place when the failure it prevents is
silent, and not otherwise.
DELETED
scaffold_solution.py (409) -- after the template fallback goes, what remains
is three commands the sibling CLI skills already document. Its one silent
mode, a project directory never registered with `projects add`, is already
unreachable: stage refuses with "maps project(s) not in <authority>".
assets/solution-skeleton + NOTES.md (261) -- only --template read them, and
the export carried a SolutionId that was not ours to reuse.
build_package.py (42) -- stage already prints its staging path and leaves the
tree, so a local zip is one documented `uip solution pack`.
next_version.py (35) -- it reported a number and prevented nothing.
entry_points.py --check -- stage re-derives on every run, so drift cannot
exist; nothing but its own tests called it.
patch_action_ttl.py --process-url -- the skill's own words: "a convenience
link, not something the runtime resolves".
_uip.uip_plain -- its only caller was `uip functions pack`, which this
pipeline deliberately never runs.
ADDED, because the review found the reverse of the ask
Both this skill and failure-signatures.md call republishing an existing
version the most expensive trap in the pipeline -- every surface reports
success and the running code does not change -- and it was guarded by prose
only. publish_package.py now computes next from the live deployment and
refuses any other version unless --force-version. A first release reports
firstRelease and accepts the version given. So the PR spent 409 lines on
scaffolding whose failures are loud and none on the failure it named worst.
FIXED, found while removing the scaffolder
`uip functions new --language ts --empty` leaves "functions": {}, and the old
scaffolder wrote that map only in template mode -- so the CLI path, the
verified primary, would have failed `uip solution pack` with "No functions
defined in uipath.json" while failure-signatures.md claimed the scaffolder
wrote it. Stage writes it now, in the staging copy, for the same reason it
writes the manifest: the map, the source and the manifest must name one file,
and anything set earlier can drift from what is actually staged.
folder-id-status is no longer a gate. It always logged `passed`, and the
PENDING_DEPLOY placeholder is the EXPECTED state between generation and
deploy, so there was never anything to fail on. A gate row that cannot fail
teaches the caller that `passed` means a check ran. It is a classifier now;
callers sequence on pairs[].deployable, which authoring's Step 2b now says.
VERIFIED, end to end, offline, on the documented path with no scaffolder
uip solution init -> .npmrc -> uip functions new -> uip solution projects add
-> jobs.map.json -> stage_jobs.py -> uip solution pack, producing
support-jobs.Function.TagOverdueTicketProcess.1.0.0.nupkg carrying
content/main.ts and content/entry-points.json whose schemas match the Studio
Web goldens. Two orderings are load-bearing and now documented: .npmrc must
precede `uip functions new` or the install 404s on the @UiPath scope, and
`projects add` is what generates the resources/solution_folder descriptors
that make the release a job.
One near-miss worth recording: the regex that removed the --check tests took
test_entry_points.py's `unittest.main()` block with them, so the suite ran zero
tests and exited 0 -- a pass, to anything reading the exit code. Restored, and
test_coded_action_scripts.py now asserts every suite in this area has an
entrypoint and at least one test.
Suites: 11 + 18 + 8 + 13 pass. Golden byte-identical on the type<T>() path.
Refused idiom still exits 1. Both CI gates green, 0 blocking verbs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implements the ontology team's handoff after an end-to-end run against the
current service. Every change below is something that run hit.
VOCABULARY
ont:language "IMPERATIVE" -> "CODED". No alias; the service refuses anything
else for a job-computed action.
ont:processType "CODED_FUNCTION" is new and REQUIRED whenever the language is
CODED. ont:language says only that a job computes the edits; this says what
kind of job, so a second runtime later needs no migration.
ont:processFolderId and ont:processUrl leave the vocabulary. An action says
what it is, what it may touch, and which process computes it -- and nothing
about where that process is deployed today.
THE DEPLOYMENT MODEL INVERTS
The deployment CREATES the folder, so it goes first: publish, deploy, then the
Data Fabric entities inside that folder, then `uip ont create --folder-key`,
then the artifacts. `deploy run` cannot target an existing folder -- given a
name that exists it makes "<name> 1" and puts the processes there, leaving
anything bound to the original pointing at zero processes.
Re-releasing is an in-place upgrade: same deployment name, new version, one
folder throughout. NEVER uninstall to re-release -- that deletes the folder and
the ontology's entities inside it. The name_taken guard that steered callers
toward uninstalling is gone; reusing the name is the correct path.
So phase 5 and its two scripts go: patch_action_ttl.py existed only to write a
folder id into an artifact, and resolve_folder_id.py only to find the value it
wrote. `uip or folders get` returns Key and numeric Id together, correcting the
claim that `processes get --all-fields` was the only source.
THREE THINGS THAT MADE EVERY FIRST RUN FAIL
Staging now strips every dependency block, the .npmrc and any lockfile. The
serverless runtime installs whatever package.json declares, cannot resolve the
@UiPath scope, and EVERY job then faults with PrepareEnvironmentError -- a
message naming nothing about dependencies. Verified: the shipped package.json
now declares none.
The npmjs 404 during `uip functions new` is documented as expected and
harmless rather than something to fix with a token. The install runs inside the
directory the command is creating, so no .npmrc can reach it, and nothing
downstream needs the SDK installed.
ont:datatype is documented and gated. Property kind is annotation-only and
never inferred from the XSD range, so a schema written to the old OWL guide
gave every class no identity property -- and every write then died AFTER the
job ran with `Entity 'X' has no identity property`, reporting rowsAffected 0,
which reads like a no-op. New gate entity-identity-declared requires exactly
one `key` property per written entity.
ALSO CORRECTED
xsd:anyURI cannot compile against the mapping; xsd:boolean is dropped by the
reasoner. Both are out of the type tables, with the reason recorded.
A read cannot filter a child by its parent's key: three constraints close every
route, so it is `SELECT * FROM {{Child}} LIMIT n` plus filtering in the job --
and the job must refuse a read that came back at the limit.
Written values cannot carry control characters; audit trails join with " | ".
`uip ont artifact validate` needs the fileName positional, and the field is
Data.Valid, capitalised. Both cost a diagnose cycle.
ontology_preflight classified actions on a fixed-space literal, so every
guide-conformant (column-aligned) file was reported as a function and
ACTION_CONTRACT passed vacuously. Now a regex.
ONE PLACE I DID NOT FOLLOW THE HANDOFF
It says `uip functions new` registers the project itself and `projects add`
now fails. On CLI 1.200.0 it does not register, and `projects add` succeeds.
The behaviour is version-dependent, so the skill verifies the .uipx and adds
only what is missing rather than committing to either.
DRY RUN, offline, on the new vocabulary
preflight PASS (10 gates) -> solution init -> functions new -> verify+register
-> jobs.map.json -> stage (manifest derived, functions map written,
devDependencies stripped) -> `uip solution pack` Success, shipping
content/main.ts and content/entry-points.json with no dependencies and no
.npmrc. Publish and deploy dry runs both clean.
Also hardened: an unresolvable UIP_CLI now reports as JSON on stderr rather than
a Python traceback.
Suites: 11 + 20 + 8 + 13 pass. Verb gate 0 blocking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s before its handler
Found by the first true end-to-end run: deploy to Orchestrator, create the
ontology against the folder the deployment made, and invoke with sample data.
Everything before the invoke passed. The invoke reported only
Running job ✗ ended in state Faulted
and the real cause was on the Orchestrator job record, nowhere else:
ErrorCode JsCodedFunction.ValidationFailed
Info ticket.0.Tags: must have required property 'Tags'
The row interface declared `Tags: string`, so the derived manifest marked it
required, so the platform rejected the input BEFORE the handler ran -- and
nothing the job would have logged exists. The read had returned that column
under a different spelling: the same entity answers `Tags` through the Data
Fabric records API and its schema field name through the ontology's own read, so
a required row field is a guess about something not knowable at authoring time.
Every field on a row interface is now optional (`Tags?: string`). Optional keeps
the documentation -- the manifest still lists the properties, so a reader sees
the shape the job expects -- while letting a differently-spelled column arrive as
undefined instead of a rejection. Verified: an optional field stays out of
`required` and stays in `properties`. The guide states the rule with the real
error text, the template and the worked example follow it, and the preflight
fixture does too.
The two golden fixtures deliberately keep the older required form: their whole
value is proving the deriver reproduces what Studio Web produced for those exact
inputs, and changing the input would destroy the comparison. Their README now
records why, so nobody "fixes" them later.
failure-signatures' ValidationFailed entry is rewritten around the real
diagnosis, including the command that surfaces it -- `uip or jobs list
--all-fields` -- since the invoke's own trace does not carry it.
What the run confirmed rather than changed: the version guard refused a
republish against a live feed, naming the computed next version; re-release with
the same deployment name upgraded in place across three versions with one folder
and no duplicate; `uip or folders get` returns Key and Id together; staging's
stripping is what makes the job runnable at all; `validate` needs the fileName
positional and the field is Data.Valid; and the mapping upsert is what moves the
ontology to DEPLOYED.
The bar, met on the local service with real data: one invoke changed a row
('payments' -> 'payments,TICKET_OVERDUE'), and a second reported rowsAffected 0
with outcome NO_CHANGE, stopping before any write.
Suites: 11 + 20 + 8 + 13 pass. Payload golden unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Making row fields optional was right -- a required field becomes `required` in the derived manifest and the platform rejects the input before the handler runs -- but the handlers were left reading straight off the interface, where the fields are now `string | undefined`: tagOverdueTicket.ts(55,30): error TS2345 tagOverdueTicket.ts(55,57): error TS2538 Both handlers now use the `column()` accessor the contract guide already prescribes, and the two columns tagOverdueTicket cannot proceed without are checked in the handler so the error can name them and say what the row did carry. flagBigOrder additionally had its row fields still declared required, which is what the guide forbids in bold; its docstring justified it. This was invisible because the `typecheck` gate skips itself when no compiler is reachable and the CI job installed no Node, so the gate had never once run. The job now installs TypeScript, and a new test compiles every complete job in the modeler's references -- templates and fragments excluded by construction, so the contract guide's skeleton is not held to it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`package.json:files` is an allowlist and `tools/` was not on it, so on any machine that installed the plugin none of it existed. `scripts/_staging.py` walks parents for `tools/entry_points.py` and dies when it cannot find it, so Phase 2 -- the deploy skill's first script -- failed at the first step. The two preflight utilities were unrunnable for the same reason; those predate this work but the deriver is the first shipped script to hard-require an unshipped file. Publishing it rather than moving it under a skill, because CLAUDE.md's self-containment rule forbids a skill reading another skill's files and entry_points.py is read by both the deploy skill's staging step and the modeler validator's contract gate. Shared, non-skill code has to live outside skills/. tools/ holds nothing but this family, and tests/ still does not ship. Verified by packing the tarball, extracting it, and running both entry points from the installed tree: stage_jobs.py reaches its SOLUTION_SRC check and the validator passes 10 of 10 gates. The new test asserts every path the ontology skills invoke both exists and sits under a published root, so the next such omission fails in CI rather than on a user's machine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s that could not execute
Documentation that still described removed mechanisms, and instructions that
could not be followed.
The deploy skill's opening paragraph, its routing table and deploy_release.py's
module docstring all still said the folder is patched into the action TTL. The
patch phase was removed; the same file says so 260 lines later, which is not
where a reader starts. That docstring also still denied the in-place upgrade its
own function docstring documents 20 lines below it.
Authoring's folder ordering contradicted itself four ways: the Run checklist said
never pick an existing folder, Phase 1 had the user pick one and gated on the
key, Phase 2 created entities in a folder that does not exist yet on the coded
path, and Step 3a still passed a variable nothing had set. Phase 1 now branches
on Step 1's classification -- Path A picks a folder, Path B collects a name and
parent and leaves the key to Step 2b -- so both paths converge on one variable.
Two failure-signature entries gave opposite advice on the same npmjs 404, one
told the reader a row interface must declare no fields (the contract guide says
optional, and keeping them is the point), and one stated as absolute what
SKILL.md documents as version-dependent. Three places claimed a scaffold writes
an .npmrc; nothing does, and staging strips it.
Every script invocation was a bare relative `scripts/x.py` on a non-executable
file, from a `cd {workdir}` where that path does not resolve. Now
`python3 <SKILL_DIR>/scripts/...` and `python3 <TOOLS_DIR>/...`, both defined
where used, matching uipath-planner and uipath-coded-apps. A test rejects the
bare form.
Also: `Skill` added to authoring's and the modeler's allowed-tools, since both
delegate and neither could call anything; the modeler now refuses to upload a
CODED action with no live release, which standalone mode could previously ship;
publish_package.py's --describe declares --force-version, the flag that
overrides the version guard, and a test now holds every script's contract to its
argparse; and the Step 2b preflight rerun is gone -- nothing about the pair
changed, so it re-reported the modeler's verdict as fresh evidence.
Two real bugs: _mask_ts raised IndexError on a source ending in a backslash
inside an unterminated string, and had a ternary whose branches were identical;
check_fields declared a 2-tuple it unpacks as three.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `support-zod/` fixture was three files, two of them byte-identical copies of the good fixture's `.ofn` and `.ttl`. Only the job ever differed, so the schema was a second thing to keep in step for no coverage. The job moves to `fixtures/variant-jobs/tagOverdueTicket.standard-schema.ts` and a `workdir_with_job()` helper swaps it over the good pair, matching how every other test in the file mutates one thing at a time. Same 22 tests, same assertions. The gate itself stays. The contract guide already states the rule and shows the idiom, but `z.object()` is the SDK's own documented, supported form, so this is a rule competing with the model's priors rather than one it has not been told. And the failure is the silent kind: a Standard Schema contract cannot be lowered, so staging would keep a manifest belonging to another job and the deploy would succeed at every visible step before faulting at invoke on additionalProperties. The second test is the one worth keeping deliberately -- it pins that input-matches-marker skips and points at input-strictness rather than failing too, so one cause reports one blame site. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No behaviour change; 58 tests unchanged and still green with and without a TypeScript compiler. Fixtures probe, examples teach. The zod variant carried a full SLA computation copied from the good pair, none of which the two tests it serves care about -- they need a zod contract and edits that writes-cover-edits can trace. Its handler is now shape-only, with a note saying so, so the contract is the only thing under test. The worked examples keep their real logic: an agent copies those. Dead code: `schemas_of()` had no caller; `parse_action` returned `subject` and `returns`, and `written_edits` returned `entities`, none of them read anywhere -- the accumulating set went with the last one. Three copies of the same three facts about `uip solution pack` / `uip functions pack` / the derived manifest lived in _staging.py's module docstring, in entry_points_module()'s, and in an inline comment in pack(). The module docstring keeps it; the other two now carry only their local point -- why the deriver is path-loaded rather than imported, and why `-n` is mandatory. The deploy SKILL.md restated pipeline.md's entry-points paragraph and failure-signatures.md's prepare-step paragraph nearly verbatim. It is the router, so it now states each rule and points at the reference that owns it. tools/README.md had grown an essay per tool, restating the gate list, the type<T>() rationale, the Standard-Schema refusal and the identity rule from the guides. Back to the terseness of the ontology_preflight section it was modelled on: what each tool is, how to run it, who calls it, and a pointer for the reasoning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the base action contract The two action guides had the same section skeleton -- what the skill needs to know, PDD table, generated TTL, validation rules -- and restated the same envelope: the two-prefix rule, the fno:Function node with ont:kind "ACTION", label and comment, the fno:expects param blocks, the rowsAffected output, the PDD type mapping. Four of the coded guide's fourteen validation rules were the base guide's verbatim. action-table-contract-guide.md now says it is the base for every action kind, and the coded guide says it is the delta: the envelope holds, the type mapping holds, every base rule holds, and what follows is only what a coded action adds. Its rules now read as "these two widen because a coded action is not single-entity, and these are additional", so a fix to a shared rule lands in one place and a third action language has an obvious slot. Net +10 lines across the two files. The overlap was structural rather than textual -- a shingle diff found only four identical lines -- so naming the inheritance costs slightly more than the four rules it removes. Worth it for the one-place-to-fix property, not for the size. Also fixes a pre-existing defect the comparison surfaced: the action template in functions-patterns-guide.md omits fno:returns entirely, while the same file's Common Mistakes table requires it and the base guide's rule 6 calls it mandatory. An action copied from that template is rejected with "function declares no outputs". Added the output block and named the base guide as the authority for the envelope, so the third copy cannot drift again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… silent gates Found by running the skills cold against a fresh coded-action request, with no context beyond the files on disk. The blocking one is mine, from the Path B folder reordering. A mapping binds each class to an entityId and folderId read from `uip df entities list`, but Path B defers `uip df entities create` until Step 2b has the folder -- and Step 2b runs after Step 2, which is where generation writes the mapping. So the documented sequence could only produce a mapping full of ids that do not exist. Step 2 now holds the mapping back on Path B (MAPPING_STATUS: defer, which preflight already reports as GENERATE_MAPPING and passes), and Step 2b generates it after reading the new entities' ids. Path A is unchanged. Preflight passed that mapping. The terms gate only checks that `ont:` names are declared, so placeholder GUIDs read as PRESENT_VALID and the first symptom would have been a deployed ontology with dead bindings. It now cross-checks every entityId/folderId the mapping binds against the handoff's CLASS_MAP -- exact rather than heuristic, and silent when no handoff supplies real ids. IRI_CONSISTENCY rejected the section-header comment form the OWL guide itself prescribes -- `# Data Property: <https://ontology.uipath.com/demo#Order.status>` -- because it compared full term IRIs against the four allowed *base* IRIs. It now compares the base, so a term under the right base passes and one under a wrong base still fails. Both directions tested. The undocumented rename: authoring collects FOLDER_NAME and hands it over, but the deploy skill has no such variable -- the folder name IS the deployment name, which only `deploy_release.py` says, and omitting the positional silently creates a folder named after the solution instead. Named in both skills. Also: the Phase 3 command block never set SOLUTION_SRC, so it exited on the first line as pasted; `xsd:boolean` was forbidden in the checklist and then used in the Phase 4 example an agent pattern-matches from, and mapped from `Boolean` in the base action guide, with no gate to catch the silently dropped range; the coded guide never mentioned that its own entity-identity gate depends on a schema annotation documented in a third guide; and _solution.py still claimed a new version means a new folder, three lines above PARENT_FOLDER_PATH. test_ontology_preflight.py could not import on Python < 3.10 -- `X | None` in a signature is evaluated at def time and the file lacked the future import that the tool has. CI's 3.13 hid it. 92 tests now pass, on 3.9 and 3.13. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ht mode that can pass
From a timed end-to-end run against a live alpha tenant: 17m19s, everything
through DEPLOYED worked, and the two costliest problems were both documentation.
**Invoke was documented nowhere.** Eighteen mentions of the word across the three
ontology skills -- "8. invoke", "every release reports ready before any invoke" --
and not one route, command or example. `uip ont` has no invoke verb, so the only
way forward was to read ActionController in the service repo and then probe URL
prefixes. It is the last step of every run and the only point where a reader has
to leave the plugin. Authoring gains a Step 4 with the call, where baseUrl/org/
tenant come from, the `ontology_` service segment (`datafabric_` 404s), the step
trace the error guidance already refers to by name, and that rowsAffected 0 with
no failed step is a success.
**The Path B preflight instruction I added this morning cannot pass.** It claimed
`--mapping-mode auto` reports GENERATE_MAPPING for a deferred mapping; auto treats
an absent mapping as one to generate and so demands entityId and folderId, which
Path B does not have until the deployment has made the folder. It returns
BLOCKED_AMBIGUITY. Rather than tell readers to ignore a red gate -- worse than no
gate -- `--mapping-mode defer` now names that state: mapping absent is expected,
status DEFERRED, every other gate still enforced. The test pins both halves, since
defer is only safe if auto still fails the same input.
Also from the run: FIELD_METADATA's shape was a `{...}` placeholder in two skills
and had to be read out of the validator (it is keyed by the property name after
the dot, one field marked identifier); the Data Fabric field-type vocabulary was
undocumented, where TEXT is rejected and DATETIME is accepted and then unrenderable,
so the XSD table gains a third column; and the invoke failure the run actually hit
is now a failure signature.
That failure is an environment gap, not a skill one, and it is worth stating
plainly: `ONTOLOGY_ORCHESTRATOR_BASE_URL` defaults to empty and nothing under
deploy/ sets it on any environment, so no coded action can be invoked on alpha or
prod today. The job itself is fine -- started directly with the payload the
platform would have sent, it returned the correct edits on both branches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… keys
Second timed run against alpha: 12m48s cold start to a DEPLOYED ontology with a
ready release and seeded data, zero retries in every phase up to that point. The
previous run's fixes all held -- --mapping-mode defer, the FIELD_METADATA shape,
the Data Fabric type vocabulary -- and the gates passed 12/12 and 10/10 first try.
It still could not invoke, and that was my fault. The Step 4 I added last time
documents the route and is careful that baseUrl/org/tenant come from `uip login
status` and nowhere else -- then leaves `Bearer {token}` with no source. `uip login
status` reports identity but no token, so the documented call cannot be made, and
reading the CLI's credential store is not an acceptable substitute.
So tools/invoke_action.py: resolves the session the same way the skills do, takes
the bearer from `uip login refresh` (which exists to emit one for programmatic
callers), prints the step trace with outcome and rowsAffected, and exits non-zero
only when the call failed or a step did. Step 4 now calls it.
Proven on the run's own leftover artifacts. The High/Open ticket went DueAt
2026-12-31 -> 2026-09-02 (High = 24h from openedAt) with OVERDUE appended, v1 ->
v2; the Closed control returned rowsAffected 0, outcome NO_CHANGE, no failed step.
Both branches, live data.
Two things the script found that no reader would have: urllib's default User-Agent
is rejected by the edge WAF with 403 "error code: 1010" before reaching the
service, and a release's first ever invoke can 504 while the serverless runtime
prepares the environment -- the second call runs warm. Both are now handled and
documented.
Corrected response keys, which were wrong in three places and would have broken
any Path A run: entities come back with `Id` and `FolderId`, not `Data[].ID` and
`Data[].FolderKey`, and the mapping guide's claim that "Data Fabric uses FolderKey
in the API response" is the reverse of the truth -- `--folder-key` is a request
flag. Native vs federated is the top-level `EntityType` and per-field
`Fields[].IsExternalField`; there is no `externalFields` key.
Also: `defer` was required by authoring's Path B but absent from the modeler's own
handoff contract, which tells it to reject an incomplete handoff -- so a modeler
reading its contract strictly would refuse the mandated Path B call. A creation
timestamp the domain reasons about now has naming guidance, instead of colliding
with the ban on modelling CreatedAt. And the deploy skill's command block gained
the dry-run line its own prose promises.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ests to the rubric Third timed run, this time a mixed inventory on a second org: 15m41s, all four actions deployed and invoked, every one correct on live data. The rubric classified them 2 SQL / 2 CODED unaided and every skill-instructed step worked first try -- both preflights, all seven backend validations, all three upload tiers. The invoke script worked without help, which is the first time an agent following the docs has reached the end. The mixed case, which nothing had positively tested before, added no friction: one CODED action forces Path B for the whole inventory, so the two SQL actions inherited the deploy-first ordering and MAPPING_STATUS: defer they would not have needed alone, and all four TTLs rode Tier 2 indistinguishably. `NO_CHANGE` on a closed ticket and on a repeat invoke both reported rowsAffected 0 with no failed step -- the converged no-op, and idempotence. The one thing that would have hurt on a harder failure was mine. invoke_action.py projected each step to label/status/durationMs and dropped `steps[].error`, which is the only diagnostic a failed invoke carries. A caller got "Executing write / failed" and nothing else; the service was in fact saying "recordId was not found in the Data Service entity". Now carried, verified by reproducing a real 400 against the tenant. Routing sent the requests that need the rubric to the skill that lacks it. The SQL/CODED rubric exists only in authoring, and a CODED verdict forces Phase 1's Path B ordering that only authoring carries -- yet both skills routed "plain domain description" to the modeler, whose standalone Step 1 picks a folder up front, the wrong order for a coded inventory. The condition is now the presence of a write operation, not the presence of files. Also from the run: a non-string column's TYPE is as much a guess as its spelling, so the contract guide now says to declare only string columns and coerce numerics through the index signature -- the run hit PascalCase columns against a camelCase schema and the tolerant accessor saved it; `ont:` binds to the ontology namespace in constraints and mapping but the platform namespace in actions, which is deliberate and now stated where the silent failure is; authoring says which confirmations may be auto-accepted with no user present and which two may not; and the modeler's "gate 8" is labelled as the separate binary it is, since ontology_preflight's gate_results contain no coded-action gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds coded actions to the ontology skill family: an author states a business rule in prose, the
agent decides whether it is expressible as declarative SQL, and generates a TypeScript job plus a
coded-action TTL when it is not. Extends
uipath-ontology-authoringanduipath-ontology-modeler,adds one new skill for the Orchestrator leg, and adds a shared validator.
Base:
feat/ontology-onboarding. Not formainwhile the ontology product is in development.What
An Ontology action today is one authored SQL statement with
{{Entity.field}}placeholders. Thewrite surface is literals-only by design, so an action whose value has to be computed cannot be
expressed at all. Coded actions move that computation into a job on Orchestrator and keep the commit
boundary declarative, so the mutation surface stays as bounded as it was.
Authoring classifies each extracted write operation on a four-test rubric, first match wins:
Ambiguous cases default to SQL.
setStatus(id, status)stays declarative;tagOverdueTicket(clock arithmetic, tag-list append, converged no-op) does not.
The new skill
uipath-ontology-coded-action-deployowns the Orchestrator leg only and never touchesuip ont,mirroring the modeler's never-upload rule. Six phases: scaffold a
{name}-jobsSolution, stage eachjob into its project's
functions/, pack and publish and deploy, resolve the numeric folder id,patch it into the TTL over the
PENDING_DEPLOYplaceholder, hand the patched paths back. ThreePython scripts, dry-run by default with
--executegating every mutating path.Authoring gains one sequencing step between the modeler's return and the Tier 2 upload: when coded
actions exist, delegate here, then re-run the validator on the patched TTLs before uploading. A
SQL-only inventory skips the whole leg.
Packaging: the manifest is derived
uip solution packrequires each project'sentry-points.jsonand never produces one. The commandthat would produce it,
uip functions pack, cannot lower thetype<T>()contract idiom on any SDKversion (0.4.4, 0.5.0, 0.6.4): only Studio Web's packer carries that derivation walker.
The verified pipeline sidesteps it. The ontology repo's working skill runs
uip solution pack,publishanddeploy runand never callsuip functions pack; its solution came from a StudioWeb export whose manifest was already derived, and
solution packonly zips a directory and readsno TypeScript.
tools/entry_points.pydoes that derivation here, reproducing byte-for-byte whatStudio Web produced for both verified jobs. Those manifests are committed as goldens and are the
only evidence of what the platform accepts.
So jobs keep the
type<T>()interfaces the existing jobs use, the interfaces stay the singlesource of truth, the manifest is regenerated on every stage so the two cannot drift, and nothing
in stage, pack, publish or deploy runs an installer. A contract outside the lowerable grammar is
refused rather than approximated, and the validator runs the same deriver as a gate so that fails
at authoring time instead of at pack time.
The spike also settled that a CLI-scaffolded Solution yields a real job release
(
ProcessType: Function,ProcessKey: {Solution}.Function.{Name}), not the HTTP-endpointflavour, which confirmed CLI scaffolding as the primary path and left the shipped template
skeleton as a fallback.
Federated entities are readable and writable
The branch said federated classes were read-only and could not be targeted by write actions. That
is wrong, and the modeler's own reference guides already said so. Corrected in authoring's federated
rules, the modeler's standalone-mode paragraph, and the mapping guide.
readOnlysurvives inCLASS_MAP as an explicit per-source exception the author states, never inferred from federation. The
source system stays the authority on whether a write succeeds, and a rejection surfaces as an
upstream error on the failing step.
skills/uipath-platform/references/data-fabric/still states federated rows are read-only. That isthe
uip df recordssurface rather than the FQS path, so it was left alone; worth a word with thatskill's owners on whether it holds there.
Validator
tools/coded_action_preflight.py, dependency-free, JSON shaped likeontology_preflight.py. Ninegates over each TTL and job pair: marker resolution, input equality against the marker, writes
attribution per edit, fields existing in the local
.ofn, zod strictness,PENDING_DEPLOYstate,and an optional typecheck that reports
skippedwhen npx is absent. The gate that matters most iswrites attribution:
ont:writesis the union over every branch the job could take, and an editoutside it is refused whole at
Preparing write statementafter the job has already run. The guidesays to declare the worst case, because over-declaring is free and under-declaring fails closed at
invoke time.
Unrecognized property shapes route to
unresolvedand fail the gate rather than reading as nowrites, which is the one place a validator like this can quietly do real damage.
Tests
tests/coded_action_preflight/, 15 tests, real subprocess and real files, no mocks. 14 pass and thetypecheck test skips without
tsc. Two assert the fail-closed posture directly: an absent schemaskips the field gate rather than passing it, and untraceable edit properties fail rather than
reading as no writes.
Known gaps
Found by a review pass over the finished work, all reproduced, none yet fixed:
folder-idandawaitresolve the solution name before dispatchSOLUTION_SRC, contrary to its own docsdeployments()returns[]on a faileddeploy listfolder-id-statusnever failsPENDING_DEPLOY" is not enforceable by the command it prescribesont:writeswritten as repeated predicate linesAlso outstanding: the coded-action pair tests sit at
tests/coded_action_preflight/where thesibling tool's live at
tests/scripts/, so no CI job picks them up (the deriver's tests are attests/scripts/and are triggered). The new skill has no activation dataset where every siblingontology skill has one, and no ontology skill yet appears in
assets/skill-status.json, the READMEtable, or CODEOWNERS; the family needs registering before this merges.
🤖 Generated with Claude Code