Skip to content

feat(ontology): add coded actions for writes SQL cannot express [DS-9390] - #2987

Open
nathanverghese-uipath wants to merge 34 commits into
feat/ontology-onboardingfrom
ontology-coded-actions
Open

feat(ontology): add coded actions for writes SQL cannot express [DS-9390]#2987
nathanverghese-uipath wants to merge 34 commits into
feat/ontology-onboardingfrom
ontology-coded-actions

Conversation

@nathanverghese-uipath

Copy link
Copy Markdown

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-authoring and uipath-ontology-modeler,
adds one new skill for the Orchestrator leg, and adds a shared validator.

Base: feat/ontology-onboarding. Not for main while the ontology product is in development.

What

An Ontology action today is one authored SQL statement with {{Entity.field}} placeholders. The
write 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:

Test Verdict
The value must be computed from stored data, the clock, or string construction CODED
It reads before writing, branches, loops over rows, writes several rows, or may write nothing CODED
A rule depends on a fact the caller must not be trusted to assert CODED
Otherwise: single entity, single record, caller-supplied literals SQL

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-deploy owns the Orchestrator leg only and never touches uip ont,
mirroring the modeler's never-upload rule. Six phases: scaffold a {name}-jobs Solution, stage each
job into its project's functions/, pack and publish and deploy, resolve the numeric folder id,
patch it into the TTL over the PENDING_DEPLOY placeholder, hand the patched paths back. Three
Python scripts, dry-run by default with --execute gating 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 pack requires each project's entry-points.json and never produces one. The command
that would produce it, uip functions pack, cannot lower the type<T>() contract idiom on any SDK
version (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,
publish and deploy run and never calls uip functions pack; its solution came from a Studio
Web export whose manifest was already derived, and solution pack only zips a directory and reads
no TypeScript. tools/entry_points.py does that derivation here, reproducing byte-for-byte what
Studio 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 single
source 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-endpoint
flavour, 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. 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.

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, so it was left alone; worth a word with that
skill's owners on whether it holds there.

Validator

tools/coded_action_preflight.py, dependency-free, JSON shaped like ontology_preflight.py. Nine
gates 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_DEPLOY state,
and an optional typecheck that reports skipped when npx is absent. The gate that matters most is
writes attribution: ont:writes is the union over every branch the job could take, and an edit
outside it is refused whole at Preparing write statement after the job has already run. The guide
says to declare the worst case, because over-declaring is free and under-declaring fails closed at
invoke time.

Unrecognized property shapes route to unresolved and fail the gate rather than reading as no
writes, 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 the
typecheck test skips without tsc. 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.

Known gaps

Found by a review pass over the finished work, all reproduced, none yet fixed:

Gap Effect
folder-id and await resolve the solution name before dispatch Phase 4 exits 1 without SOLUTION_SRC, contrary to its own docs
Read-vs-param classification is computed but never consumed by a gate A read declared as a scalar passes every gate
Computed property keys and object spreads are not detected An undeclared write passes, then is refused at invoke time
deployments() returns [] on a failed deploy list The idempotence guard fails open and would create a second deployment
folder-id-status never fails Authoring's "require no PENDING_DEPLOY" is not enforceable by the command it prescribes
ont:writes written as repeated predicate lines Valid Turtle, rejected with a message that blames the job

Also outstanding: the coded-action pair tests sit at tests/coded_action_preflight/ where the
sibling tool's live at tests/scripts/, so no CI job picks them up (the deriver's tests are at
tests/scripts/ and are triggered). The new skill has no activation dataset where every sibling
ontology skill has one, and no ontology skill yet appears in assets/skill-status.json, the README
table, or CODEOWNERS; the family needs registering before this merges.

🤖 Generated with Claude Code

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>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Claude finished @nathanverghese-uipath's task in 3m 7s —— View job


PR Review: feat(ontology): add coded actions for writes SQL cannot express

  • Read review criteria and project conventions
  • Analyze full PR diff
  • Read existing PR conversation
  • Review new skill structure (uipath-ontology-coded-action-deploy)
  • Review modified skills (authoring, modeler)
  • Review Python tools and validators
  • Review test coverage
  • Check CODEOWNERS, skill-status.json, cross-skill references
  • Verify links and paths
  • Compile findings and post review

Summary

Adds a new skill uipath-ontology-coded-action-deploy for shipping computed write actions to Orchestrator, extends uipath-ontology-authoring and uipath-ontology-modeler to support a SQL/CODED classification rubric and the deploy delegation, and adds two shared Python tools (coded_action_preflight.py, entry_points.py) with tests. Well-structured, internally consistent, and the PR description is unusually thorough in documenting known gaps.

Change-by-Change Review

1. skills/uipath-ontology-coded-action-deploy/SKILL.md (new skill)

Severity: OK with notes

Frontmatter is valid: name matches folder, description is ~448 chars (well under 1024), starts with a use-case signal rather than a metadata tag. Has when_to_use, allowed-tools, and user-invocable. The description front-loads the Orchestrator/deploy/coded-action signals.

Body is well organized: routing boundary table, input table, dry-run-by-default policy, six numbered phases, scripts reference, "when something breaks" pointer, and a boundaries section. The phase structure is prescriptive and numbered. Reference navigation links to references/failure-signatures.md and references/pipeline.md — both exist.

Missing: no explicit redirect for commonly confused sibling skills in the description field. The body's routing table covers this well, but the frontmatter description should include compact redirects per skill-structure rules (e.g., For artifact generation→uipath-ontology-modeler. For ontology upload→uipath-ontology-authoring.).

2. skills/uipath-ontology-coded-action-deploy/references/pipeline.md

Severity: OK

Comprehensive reference documenting the value shapes, staging mechanics, publish/deploy model, and the two dead ends. No duplication with SKILL.md — SKILL.md has the sequencing, this has the shapes and the reasoning. Clean separation.

3. skills/uipath-ontology-coded-action-deploy/references/failure-signatures.md

Severity: OK

Twelve failure signatures, each with symptom/cause/fix. Genuinely useful for an agent that encounters these misleading error messages. No overlap with the pipeline reference beyond single-sentence cross-references.

4. skills/uipath-ontology-coded-action-deploy/scripts/solution_scaffold.py

Severity: OK

Clean Python, stdlib-only. Two modes (CLI and template), both dry-run-by-default, both idempotent. Writes .npmrc with env-var references, never literal tokens. The jobs.map.json contract is clear.

5. skills/uipath-ontology-coded-action-deploy/scripts/solution_release.py

Severity: Medium

Well-structured with subcommands. Two bugs the PR description already flags:

  • deployments() fails open (solution_release.py:148-150): allow_fail=True means a failed deploy list returns [], and live_at_version() returns None, so the idempotence guard at line 387 does not fire. A second deploy would create a duplicate folder. The PR description notes this. Fix this →

  • folder-id and await resolve solution_name() before dispatch (solution_release.py:481): name = solution_name() is called for every subcommand, but solution_name() falls back to solution_src().name, which dies if SOLUTION_SRC is unset. So folder-id and await (read-only commands) fail if SOLUTION_SRC is missing, contrary to the docstring at line 91-93 which says they work without it. The PR description notes this too. Fix this →

6. skills/uipath-ontology-coded-action-deploy/scripts/ttl_patch.py

Severity: OK

Focused and correct. Dry-run-by-default. Both refusals are intentional and well-documented. Idempotent.

7. tools/coded_action_preflight.py

Severity: Medium

1221 lines, well-structured validator with 9 gates. Two issues:

  • check_folder_id never fails (line 1054): log.add("folder-id-status", "passed") is called unconditionally before any check. The gate always reports passed regardless of what the folder id actually is. This means authoring's "require no PENDING_DEPLOY" gate is not enforceable through this tool's gate status — callers must read pairs[].deployable instead. The tools/README.md documents this, but it's a design choice worth noting as it differs from every other gate's semantics. The PR description flags this as a known gap. Fix this →

  • XSD_TYPES maps number to xsd:integer (line 101): TypeScript's number is a float, not an integer. For contract checking this may be intentional (the ontology schema uses xsd:integer for quantity-like fields), but it could silently pass a mismatch if a field is xsd:decimal in the OFN. Low severity — just a note.

8. tools/entry_points.py

Severity: OK

Clean 226-line deriver. Validates the grammar strictly and refuses anything outside it. The golden test fixtures verify byte-for-byte match with Studio Web's output — strong validation.

9. skills/uipath-ontology-authoring/SKILL.md (modified)

Severity: OK

Changes are well-scoped: adds SQL/CODED classification rubric (Step 1), adds Step 2b for coded-action deploy delegation, corrects the federated-entity write rules, and threads coded TTLs through Tier 2 upload. The delegation degrades gracefully (explicit error message when the deploy skill is unavailable). The worked classification table is a strong addition.

10. skills/uipath-ontology-modeler/SKILL.md (modified)

Severity: OK

Minimal, correct changes: adds jobs/{actionName}.ts to the artifact list, adds the coded-action contract guide to the reference navigation, adds gate 8 (coded-action contract gate), updates "seven" to "eight" gate results. Updates the federated-entity language to match authoring. Adds the coded-action contract reference link — and the file exists.

11. skills/uipath-ontology-modeler/references/coded-action-contract-guide.md

Severity: OK

Comprehensive contract specification covering the TTL structure, job contract, TypeScript implementation rules, the zod idiom, validation rules, and the ont:writes declaration. Well-structured for LLM consumption with clear tables and code blocks. The PDD table mapping is a good shortcut for structured inputs.

12. skills/uipath-ontology-modeler/references/coded-action-example.md

Severity: OK

Two complete worked examples (tagOverdueTicket and flagBigOrder) showing TTL + job pairs with detailed annotations. Correctly gated behind "read only when a gate fails." The examples are the verified-live sources, which is strong evidence.

13. skills/uipath-ontology-modeler/references/mapping-yarrrml-guide.md (modified)

Severity: OK

Three-line diff changing "read-only" federated language to "readable and writable" — consistent with the authoring and modeler changes.

14. tests/coded_action_preflight/ (15 tests)

Severity: Medium — misplaced

The tests are well-written: real subprocess, real files, no mocks, one mutation per gate. The assert_only_gate_fails helper is clean. 14 tests plus 1 conditional typecheck skip.

However, as the PR description itself notes, these tests sit at tests/coded_action_preflight/ where sibling tool tests live at tests/scripts/. No CI job picks them up from this location. They should be moved to tests/scripts/ or the CI config updated.

15. tests/scripts/test_entry_points.py

Severity: OK

Golden derivation tests, fail-closed tests, and write/check round-trip tests. The test_check_reports_the_golden_manifest_as_current test is the key integrity check. Well-located under tests/scripts/.

16. tests/tasks/uipath-ontology-modeler/_shared/fixtures/entry-points/

Severity: OK

Two golden manifests and their source jobs, with a README explaining their provenance. These are the "only evidence of what the platform accepts" — correctly committed as test fixtures.

17. tools/README.md (modified)

Severity: OK

Adds documentation for coded_action_preflight.py and entry_points.py. Accurate and well-compressed.

18. skills/uipath-ontology-coded-action-deploy/assets/ (solution-skeleton + NOTES.md)

Severity: OK

The skeleton is a real Studio Web export's manifests. NOTES.md meticulously documents every renamed field and the SolutionId warning. No secrets or personal paths.

What's Missing

  • CODEOWNERS: No entry for any uipath-ontology-* skill path. High per review criteria. All three ontology skills (uipath-ontology-authoring, uipath-ontology-modeler, uipath-ontology-coded-action-deploy) and tests/tasks/uipath-ontology-modeler/ need owners.
  • assets/skill-status.json: No entry for any ontology skill. High — CI's check-skill-status.py enforces every skill has a manifest entry.
  • E2E test tasks: No tests/tasks/uipath-ontology-coded-action-deploy/ directory exists. No smoke or e2e task YAMLs for the new skill. Medium — the new skill has no activation dataset either, as the PR description notes.
  • tests/coded_action_preflight/ location: Tests are not under tests/scripts/ where CI runs them. Medium — the PR description flags this.
  • Frontmatter redirects: The new skill's description lacks compact redirects for sibling skills per skill-structure rules.
  • Known code bugs: The PR description documents 6 known gaps and explicitly states "none yet fixed." The two most impactful are the deployments() fail-open and the solution_name() dispatch issue (see items 5 and 7 above).

Area Ratings

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 run flags, uip or processes get --all-fields output shape, and uip solution publish --wait behavior 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-platform skill'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 for uip df records vs the FQS path.
  • The ont:writes as 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:

  1. Add CODEOWNERS entries for all uipath-ontology-* skill paths and their test paths.
  2. Add assets/skill-status.json entries for all three ontology skills (likely in-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.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ Smoke test coverage gap — the following changed skills have no tests under tests/tasks/:

  • uipath-ontologies
  • uipath-ontology-authoring
  • uipath-ontology-coded-action-deploy

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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.py to derive Functions entry-points.json from type<T>() interface contracts, with golden tests.
  • Add tools/coded_action_preflight.py and the tools/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-deploy skill + 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.

Comment thread tools/coded_action/turtle.py Outdated
Comment thread skills/uipath-ontology-coded-action-deploy/scripts/_solution.py
Comment thread tools/entry_points.py
Comment thread skills/uipath-ontology-coded-action-deploy/scripts/_solution.py Outdated
nathanverghese-uipath and others added 20 commits September 3, 2026 11:43
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants