Skip to content

Commit 8a10b1a

Browse files
fix(datafabric writes) + ontology write POC, validated on staging
Two write-path bugs found while validating the ontology POC end-to-end against live staging, both fixed: 1. Read schema stripped the Id primary key (write_validation system-field filter), so the NL-to-SQL model (a) invented a non-existent 'rowid' column for ORDER BY on paginated reads (FQS 400) and (b) never returned Id, leaving the agent no record_id for updates/deletes. Fix: retain the primary key for WRITABLE entities in the read schema (SELECT it, ORDER BY it); keep other system fields hidden; read-only entities unchanged. P3 collision guard for user/CSV fields sharing a system field name. Harden is_entity_writable with getattr. 2. Write executor called the CRUD endpoint with the entity NAME, but .../EntityService/entity/{id}/insert requires the GUID id ("not valid" 400). Fix: handler maps entity name -> id before executing, restores the friendly name on the result. Verified on staging (dataservicetest/DataFabricFQS): with both fixes the refund flow's insert + 3 updates all persist (read-back confirmed). The ontology compiles, activates, and correctly governs tool selection (RefundRequest insert-only; Order/Risk/Contact update; Customer read-only, never written). POC harness (scripts/): - poc_refund_setup.sh / poc_refund_teardown.sh — create+seed / delete the 5 refund entities, emit ontology + entity-set (referenceKey=GUID) + ids - poc_refund_drive.py — drive the real write handler with the ontology active, verify by read-back (deterministic; no LLM) - run_agent_with_ontology.py — full LLM-in-the-loop variant; gains --agenthub-config (LLM-gateway licensing OpCode; without it the gateway 403s) and recursion_limit - POC_README.md — env setup + the three run levels + the known agent-loop gap (create_agent does not auto-execute the terminal write batch; that is runtime plumbing, not the ontology/write tool) Tests: 740 passed. New: read-schema PK retention (writable vs read-only, other-system-fields-hidden, collision-not-duplicated, rowid-free ORDER BY) and name->id translation for CRUD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4ea5062 commit 8a10b1a

11 files changed

Lines changed: 602 additions & 13 deletions

File tree

scripts/POC_README.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Data Fabric Ontology Write POC
2+
3+
Demonstrates an OWL ontology driving native Data Fabric **writes** through the
4+
agent tooling: the ontology compiles into a structured intermediate
5+
representation, activates in the write handler, governs which entities/operations
6+
are allowed, and the resulting writes persist to Data Fabric.
7+
8+
Hero case: a contact-center **refund** agent over 5 entities — `Customer`
9+
(read-only), `Contact`, `PurchaseOrder`, `CustomerRisk`, `RefundRequest`.
10+
11+
## Components
12+
13+
| File | Role |
14+
|------|------|
15+
| `src/.../datafabric_tool/ontology_compiler.py` | OWL Turtle → `CompiledOntology` (entity_access, measure/state/reference fields, HITL, relationships) |
16+
| `src/.../datafabric_tool/compiled_ontology.py` | the IR model |
17+
| `src/.../datafabric_tool/datafabric_tool.py` | write tool + handler; fetches+compiles ontology, maps entity name→id for CRUD |
18+
| `src/.../datafabric_tool/write_validation.py` | writability + mutation-intent validation (ontology-constrained) |
19+
| `src/.../datafabric_tool/datafabric_prompt_builder.py` | read schema; retains the primary key for writable entities |
20+
| `scripts/poc_refund_setup.sh` | create + seed staging entities, emit ontology + entity-set + ids |
21+
| `scripts/poc_refund_drive.py` | drive the real write handler with the ontology active, verify by read-back |
22+
| `scripts/poc_refund_teardown.sh` | delete the POC entities |
23+
| `scripts/run_agent_with_ontology.py` | full LLM-in-the-loop variant (see "Known gap") |
24+
25+
## Prerequisites
26+
27+
```bash
28+
# 1. CLI auth to the target tenant (entity create/seed/verify)
29+
uip login
30+
31+
# 2. SDK env vars — the Python SDK reads these (separate from the CLI's auth).
32+
# Source the access token from a logged-in session; do NOT hardcode it.
33+
export UIPATH_ACCESS_TOKEN="$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.uipath/.auth.json')))['access_token'])")"
34+
export UIPATH_URL="https://<host>/<org>/<tenant>"
35+
export UIPATH_ORGANIZATION_ID="<org-guid>"
36+
export UIPATH_TENANT_ID="<tenant-guid>"
37+
```
38+
39+
The access token is short-lived (~1h); re-export after re-login.
40+
41+
## Run
42+
43+
### A. Ontology compiles + activates (offline, no staging)
44+
45+
```bash
46+
uv run python scripts/run_agent_with_ontology.py \
47+
--ontology ../../../df-agent-os/roadmap/p1-owl-write-extension.ttl \
48+
--entity-set scripts/sample_refund_entity_set.json \
49+
--prompt x --dry-run
50+
```
51+
52+
Prints the extracted ontology facts without any network call.
53+
54+
### B. Ontology governs writes that persist (live staging) — the working POC
55+
56+
```bash
57+
bash scripts/poc_refund_setup.sh ./poc_out # create + seed
58+
uv run python scripts/poc_refund_drive.py ./poc_out # drive writes, verify
59+
bash scripts/poc_refund_teardown.sh ./poc_out # clean up
60+
```
61+
62+
`poc_refund_drive.py` prints `ontology ACTIVE`, runs insert RefundRequest +
63+
update Order/CustomerRisk/Contact through the real handler, and verifies all
64+
four mutations by read-back.
65+
66+
### C. Full LLM agent picks the tools (live)
67+
68+
```bash
69+
set -a; source ./poc_out/refund_ids.env; set +a
70+
uv run python scripts/run_agent_with_ontology.py \
71+
--ontology ./poc_out/refund_ontology.ttl \
72+
--entity-set ./poc_out/refund_entity_set.json \
73+
--system-prompt scripts/sample_refund_sop.txt \
74+
--model gpt-4.1-2025-04-14 --agenthub-config agentsplayground \
75+
--prompt "Process the refund for contact ${CONTACT_ID}. Order id ${ORDER_ID}, CustomerRisk id ${RISK_ID}, Customer id ${CUSTOMER_ID}. ..."
76+
```
77+
78+
The LLM reads, decides, and emits ontology-correct write calls (insert on
79+
RefundRequest, update on the writable entities, never on read-only Customer).
80+
81+
## Known gap
82+
83+
In path **C**, the standalone `create_agent` harness terminates on control-flow
84+
tools and the gateway returns tool calls in the OpenAI Responses format; the
85+
terminal write batch is *planned* but not auto-executed by this harness. That is
86+
agent-runtime plumbing, not the ontology or the write tool — path **B** confirms
87+
the writes themselves land. The production `uipath_agents` runtime drives the
88+
tool-execution loop and is the place to validate path C end-to-end.
89+
90+
## Notes
91+
92+
- Status fields are plain STRING (the `choice-set-values` endpoint was
93+
unreliable on staging); the ontology still models `OrderStatus` as a
94+
`StateField`. Swap to ChoiceSet fields when the endpoint is stable.
95+
- The seeded entities are not FK-linked (simplified scenario); pass the record
96+
ids explicitly in path C rather than relying on relationship discovery.

scripts/poc_refund_drive.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"""Ontology Write POC — drive the real write tool against staging.
2+
3+
Loads the POC artifacts produced by ``poc_refund_setup.sh``, injects the
4+
ontology via the same bridge the CLI uses (monkeypatching the not-yet-shipped
5+
``EntitiesService.get_ontology_file_async``), builds the real Data Fabric
6+
tools, then runs the refund flow by invoking the write tool handler directly —
7+
the exact callable an agent's tool node calls. Proves: ontology compile +
8+
inject -> write validation -> EntitiesService CRUD -> records persisted ->
9+
verified by read-back.
10+
11+
This is the deterministic counterpart to ``run_agent_with_ontology.py`` (which
12+
puts an LLM in the loop). Use this to confirm the writes actually land.
13+
14+
Prereq: UIPATH_* env vars set (see scripts/POC_README.md), and
15+
``poc_refund_setup.sh`` already run.
16+
Usage: uv run python scripts/poc_refund_drive.py [OUT_DIR] (default: ./poc_out)
17+
"""
18+
19+
from __future__ import annotations
20+
21+
import asyncio
22+
import json
23+
import sys
24+
from pathlib import Path
25+
26+
import uipath.platform.entities._entities_service as es_mod
27+
28+
OUT = Path(sys.argv[1] if len(sys.argv) > 1 else "./poc_out")
29+
ONTOLOGY_TTL = (OUT / "refund_ontology.ttl").read_text()
30+
31+
32+
async def _get_ontology_file_async(self, file_type, *args, **kwargs): # noqa: ANN001
33+
"""Bridge: return the POC ontology for the 'owl' file type.
34+
35+
Stands in for the platform method that has not shipped yet, so the
36+
handler's own _maybe_compile_ontology picks it up naturally.
37+
"""
38+
return ONTOLOGY_TTL if file_type == "owl" else None
39+
40+
41+
es_mod.EntitiesService.get_ontology_file_async = _get_ontology_file_async
42+
43+
from uipath.agent.models.agent import ( # noqa: E402
44+
AgentContextResourceConfig,
45+
AgentContextType,
46+
)
47+
from uipath.platform.entities import DataFabricEntityItem # noqa: E402
48+
49+
from uipath_langchain.agent.tools.datafabric_tool import ( # noqa: E402
50+
create_datafabric_tools,
51+
)
52+
53+
54+
def _load_ids() -> dict[str, str]:
55+
ids: dict[str, str] = {}
56+
for line in (OUT / "refund_ids.env").read_text().splitlines():
57+
line = line.strip()
58+
if line and not line.startswith("#") and "=" in line:
59+
k, v = line.split("=", 1)
60+
ids[k] = v
61+
return ids
62+
63+
64+
class _NoLLM:
65+
"""Stub LLM — only needed to build the read tool; never invoked here."""
66+
67+
def bind_tools(self, *args, **kwargs): # noqa: ANN001, ANN002, ANN003
68+
return self
69+
70+
71+
async def main() -> int:
72+
ids = _load_ids()
73+
items = [
74+
DataFabricEntityItem.model_validate(d)
75+
for d in json.loads((OUT / "refund_entity_set.json").read_text())
76+
]
77+
by_suffix = {it.name.rsplit("_", 1)[1]: it.name for it in items}
78+
79+
resource = AgentContextResourceConfig(
80+
name="refund_context",
81+
description="Refund processing context",
82+
context_type=AgentContextType.DATA_FABRIC_ENTITY_SET,
83+
entity_set=items,
84+
)
85+
tools = create_datafabric_tools(resource, _NoLLM()) # type: ignore[arg-type]
86+
handler = next(t for t in tools if t.name.endswith("_write")).coroutine
87+
88+
await handler._ensure_initialized()
89+
onto = handler._compiled_ontology
90+
print("=== ONTOLOGY STATUS ===")
91+
if onto and not onto.is_empty():
92+
print(
93+
" ACTIVE — entity_access:",
94+
{k: sorted(v) for k, v in onto.entity_access.items()},
95+
)
96+
else:
97+
print(" INACTIVE (metadata-only)")
98+
99+
# SOP decision is fixed for the seeded scenario (Delivered, score 2, $200).
100+
amt, score, ltv = 200.0, 2, 5000.0
101+
print(
102+
f"\nDECIDE: order Delivered, risk {score} < 3, amount {amt} <= 500 -> APPROVE\n"
103+
)
104+
105+
async def write(**kw): # noqa: ANN003
106+
out = json.loads(await handler(**kw))
107+
print(
108+
f" {kw['operation']:6} {kw['entity_key'].rsplit('_', 1)[1]:14} -> success={out['success']}"
109+
)
110+
return out
111+
112+
print("=== WRITES (real handler, ontology validating) ===")
113+
await write(
114+
entity_key=by_suffix["RefundRequest"],
115+
operation="insert",
116+
fields={
117+
"ApprovedAmount": amt,
118+
"Reason": "Auto-approved: low risk",
119+
"OrderRef": ids["ORDER_ID"],
120+
"CustomerRef": ids["CUSTOMER_ID"],
121+
"RefundStatus": "Pending",
122+
},
123+
)
124+
await write(
125+
entity_key=by_suffix["PurchaseOrder"],
126+
operation="update",
127+
record_id=ids["ORDER_ID"],
128+
fields={"OrderStatus": "Returned"},
129+
)
130+
await write(
131+
entity_key=by_suffix["CustomerRisk"],
132+
operation="update",
133+
record_id=ids["RISK_ID"],
134+
fields={"RiskScore": score + 1, "LifetimeValue": ltv - amt},
135+
)
136+
await write(
137+
entity_key=by_suffix["Contact"],
138+
operation="update",
139+
record_id=ids["CONTACT_ID"],
140+
fields={"Resolution": "Approved"},
141+
)
142+
143+
# Verify by read-back through the resolved service.
144+
from uipath.platform import UiPath
145+
146+
svc = (await UiPath().entities.resolve_entity_set_async(items)).entities_service
147+
148+
def g(rec, k): # noqa: ANN001
149+
return rec.get(k) if isinstance(rec, dict) else getattr(rec, k, None)
150+
151+
order = await svc.get_record_async(by_suffix["PurchaseOrder"], ids["ORDER_ID"])
152+
risk = await svc.get_record_async(by_suffix["CustomerRisk"], ids["RISK_ID"])
153+
contact = await svc.get_record_async(by_suffix["Contact"], ids["CONTACT_ID"])
154+
155+
print("\n=== VERIFY (read-back) ===")
156+
checks = [
157+
("Order.OrderStatus", g(order, "OrderStatus"), "Returned"),
158+
("Risk.RiskScore", int(g(risk, "RiskScore")), 3),
159+
("Risk.LifetimeValue", float(g(risk, "LifetimeValue")), 4800.0),
160+
("Contact.Resolution", g(contact, "Resolution"), "Approved"),
161+
]
162+
ok = 0
163+
for label, actual, expected in checks:
164+
good = actual == expected
165+
ok += good
166+
print(f" {'OK ' if good else 'XX '} {label} = {actual} (expected {expected})")
167+
print(f"\n=== {ok}/{len(checks)} verified ===")
168+
return 0 if ok == len(checks) else 1
169+
170+
171+
if __name__ == "__main__":
172+
raise SystemExit(asyncio.run(main()))

scripts/poc_refund_setup.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/bin/bash
2+
# =============================================================================
3+
# Ontology Write POC — staging setup
4+
#
5+
# Creates the contact-center refund entities on the logged-in Data Fabric
6+
# tenant, seeds one refund scenario, and emits three artifacts into OUT:
7+
# - refund_entity_set.json (DataFabricEntityItem list; referenceKey = GUID)
8+
# - refund_ontology.ttl (OWL with df:entityKey matching the real names)
9+
# - refund_ids.env (seeded record ids + entity ids for teardown)
10+
#
11+
# Status fields are plain STRING (the choice-set-values endpoint is currently
12+
# unreliable on staging); the ontology still models OrderStatus as a StateField.
13+
#
14+
# Prereq: `uip login` to the target tenant.
15+
# Usage: bash scripts/poc_refund_setup.sh [OUT_DIR] (default: ./poc_out)
16+
# Teardown: bash scripts/poc_refund_teardown.sh [OUT_DIR]
17+
# =============================================================================
18+
set -eo pipefail
19+
OUT="${1:-./poc_out}"
20+
mkdir -p "$OUT"
21+
P="Rfnd$(date +%H%M%S)"
22+
idof() { python3 -c "import json,sys; print(json.load(sys.stdin)['Data']['Id'])"; }
23+
24+
echo "=== Entities (prefix $P) ==="
25+
CUST_E=$(uip df entities create "${P}_Customer" --body "{\"displayName\":\"${P} Customer\",\"fields\":[{\"fieldName\":\"CustomerName\",\"type\":\"STRING\",\"isRequired\":true,\"lengthLimit\":200},{\"fieldName\":\"AccountTier\",\"type\":\"STRING\",\"lengthLimit\":50}]}" --output json 2>/dev/null | idof)
26+
ORD_E=$(uip df entities create "${P}_PurchaseOrder" --body "{\"displayName\":\"${P} Order\",\"fields\":[{\"fieldName\":\"OrderNumber\",\"type\":\"STRING\",\"isRequired\":true,\"lengthLimit\":50},{\"fieldName\":\"TotalAmount\",\"type\":\"DECIMAL\",\"decimalPrecision\":2},{\"fieldName\":\"OrderStatus\",\"type\":\"STRING\",\"lengthLimit\":50}]}" --output json 2>/dev/null | idof)
27+
RISK_E=$(uip df entities create "${P}_CustomerRisk" --body "{\"displayName\":\"${P} Risk\",\"fields\":[{\"fieldName\":\"RiskScore\",\"type\":\"INTEGER\"},{\"fieldName\":\"LifetimeValue\",\"type\":\"DECIMAL\",\"decimalPrecision\":2}]}" --output json 2>/dev/null | idof)
28+
CONT_E=$(uip df entities create "${P}_Contact" --body "{\"displayName\":\"${P} Contact\",\"fields\":[{\"fieldName\":\"ContactReason\",\"type\":\"STRING\",\"lengthLimit\":50},{\"fieldName\":\"RefundAmount\",\"type\":\"DECIMAL\",\"decimalPrecision\":2},{\"fieldName\":\"OrderRef\",\"type\":\"STRING\",\"lengthLimit\":100},{\"fieldName\":\"Resolution\",\"type\":\"STRING\",\"lengthLimit\":50}]}" --output json 2>/dev/null | idof)
29+
RFND_E=$(uip df entities create "${P}_RefundRequest" --body "{\"displayName\":\"${P} Refund\",\"fields\":[{\"fieldName\":\"ApprovedAmount\",\"type\":\"DECIMAL\",\"decimalPrecision\":2,\"isRequired\":true},{\"fieldName\":\"Reason\",\"type\":\"STRING\",\"isRequired\":true,\"lengthLimit\":500},{\"fieldName\":\"OrderRef\",\"type\":\"STRING\",\"lengthLimit\":100},{\"fieldName\":\"CustomerRef\",\"type\":\"STRING\",\"lengthLimit\":100},{\"fieldName\":\"RefundStatus\",\"type\":\"STRING\",\"lengthLimit\":50}]}" --output json 2>/dev/null | idof)
30+
31+
echo "=== Seed records ==="
32+
CUST_R=$(uip df records insert "$CUST_E" --body '{"CustomerName":"Sarah Chen","AccountTier":"Gold"}' --output json 2>/dev/null | idof)
33+
ORD_R=$(uip df records insert "$ORD_E" --body '{"OrderNumber":"ORD001","TotalAmount":200.00,"OrderStatus":"Delivered"}' --output json 2>/dev/null | idof)
34+
RISK_R=$(uip df records insert "$RISK_E" --body '{"RiskScore":2,"LifetimeValue":5000.00}' --output json 2>/dev/null | idof)
35+
CONT_R=$(uip df records insert "$CONT_E" --body "{\"ContactReason\":\"Refund\",\"RefundAmount\":200.00,\"OrderRef\":\"$ORD_R\",\"Resolution\":\"Open\"}" --output json 2>/dev/null | idof)
36+
37+
F="00000000-0000-0000-0000-000000000000"
38+
# referenceKey = entity GUID — resolve_entity_set_async looks up by this value,
39+
# and the CRUD endpoints require the id, not the entity name.
40+
cat > "$OUT/refund_entity_set.json" <<JSON
41+
[
42+
{"id":"$CUST_E","name":"${P}_Customer","folderId":"$F","referenceKey":"$CUST_E","description":"Customer master (read-only)"},
43+
{"id":"$CONT_E","name":"${P}_Contact","folderId":"$F","referenceKey":"$CONT_E","description":"Inbound contact / refund request"},
44+
{"id":"$ORD_E","name":"${P}_PurchaseOrder","folderId":"$F","referenceKey":"$ORD_E","description":"Order records"},
45+
{"id":"$RISK_E","name":"${P}_CustomerRisk","folderId":"$F","referenceKey":"$RISK_E","description":"Customer risk profile"},
46+
{"id":"$RFND_E","name":"${P}_RefundRequest","folderId":"$F","referenceKey":"$RFND_E","description":"Refund records"}
47+
]
48+
JSON
49+
50+
cat > "$OUT/refund_ontology.ttl" <<TTL
51+
@prefix df: <https://ontology.uipath.com/datafabric#> .
52+
@prefix ex: <https://ontology.example.com/refund#> .
53+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
54+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
55+
56+
ex:Customer a owl:Class ; rdfs:subClassOf df:ReadableEntity ; df:entityKey "${P}_Customer" .
57+
ex:Contact a owl:Class ; rdfs:subClassOf df:WritableEntity ; df:entityKey "${P}_Contact" ; df:allowsOperation "update" .
58+
ex:Order a owl:Class ; rdfs:subClassOf df:WritableEntity ; df:entityKey "${P}_PurchaseOrder" ; df:allowsOperation "update" .
59+
ex:Risk a owl:Class ; rdfs:subClassOf df:WritableEntity ; df:entityKey "${P}_CustomerRisk" ; df:allowsOperation "update" .
60+
ex:Refund a owl:Class ; rdfs:subClassOf df:WritableEntity ; df:entityKey "${P}_RefundRequest" ; df:allowsOperation "insert" .
61+
62+
ex:field_Order_Status a df:StateField ; df:fieldKey "OrderStatus" ; df:choiceSetKey "OrderStatusValues" .
63+
ex:field_Risk_Score a df:MeasureField ; df:fieldKey "RiskScore" ; df:measureSemantics "additive" .
64+
ex:field_Risk_LTV a df:MeasureField ; df:fieldKey "LifetimeValue" ; df:measureSemantics "additive" .
65+
ex:field_Refund_Order a df:ReferenceField ; df:fieldKey "OrderRef" ; df:referencesEntity ex:Order .
66+
ex:field_Refund_Customer a df:ReferenceField ; df:fieldKey "CustomerRef" ; df:referencesEntity ex:Customer .
67+
68+
ex:Order df:hasField ex:field_Order_Status .
69+
ex:Risk df:hasField ex:field_Risk_Score , ex:field_Risk_LTV .
70+
ex:Refund df:hasField ex:field_Refund_Order , ex:field_Refund_Customer .
71+
72+
ex:CreateRefund a df:InsertAction ; df:writeOperation "insert" ; df:targetEntity ex:Refund ; df:requiresHITL false .
73+
ex:UpdateOrder a df:UpdateAction ; df:writeOperation "update" ; df:targetEntity ex:Order ; df:requiresHITL false .
74+
ex:UpdateRisk a df:UpdateAction ; df:writeOperation "update" ; df:targetEntity ex:Risk ; df:requiresHITL false .
75+
ex:UpdateContact a df:UpdateAction ; df:writeOperation "update" ; df:targetEntity ex:Contact ; df:requiresHITL false .
76+
77+
ex:Refund df:hasAction ex:CreateRefund .
78+
ex:Order df:hasAction ex:UpdateOrder .
79+
ex:Risk df:hasAction ex:UpdateRisk .
80+
ex:Contact df:hasAction ex:UpdateContact .
81+
82+
ex:Contact df:relatedEntity ex:Customer , ex:Order .
83+
ex:Refund df:relatedEntity ex:Order , ex:Customer .
84+
ex:Risk df:relatedEntity ex:Customer .
85+
TTL
86+
87+
cat > "$OUT/refund_ids.env" <<ENV
88+
CONTACT_ID=$CONT_R
89+
ORDER_ID=$ORD_R
90+
RISK_ID=$RISK_R
91+
CUSTOMER_ID=$CUST_R
92+
E_CUSTOMER=$CUST_E
93+
E_CONTACT=$CONT_E
94+
E_ORDER=$ORD_E
95+
E_RISK=$RISK_E
96+
E_REFUND=$RFND_E
97+
ENV
98+
99+
echo "=== DONE — artifacts in $OUT ==="
100+
echo "Contact=$CONT_R Order=$ORD_R Risk=$RISK_R Customer=$CUST_R"

scripts/poc_refund_teardown.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
# Delete the POC entities created by poc_refund_setup.sh.
3+
# Usage: bash scripts/poc_refund_teardown.sh [OUT_DIR] (default: ./poc_out)
4+
set -eo pipefail
5+
OUT="${1:-./poc_out}"
6+
set -a; source "$OUT/refund_ids.env"; set +a
7+
for e in "$E_CUSTOMER" "$E_CONTACT" "$E_ORDER" "$E_RISK" "$E_REFUND"; do
8+
uip df entities delete "$e" --yes --reason "poc teardown" --output json 2>/dev/null \
9+
| python3 -c "import json,sys;print(json.load(sys.stdin).get('Code','?'))" 2>/dev/null || echo "skip $e"
10+
done
11+
echo "teardown done"

0 commit comments

Comments
 (0)