Subaccount Migration Orchestrator
Orchestrate complete Neo subaccount configuration migration to Cloud Foundry.
Purpose
This orchestrator collects all required inputs upfront, then invokes the subaccount migration skills in the correct sequence to migrate all platform-level configuration from a Neo subaccount to a CF subaccount:
- Trust —
subaccount-trust-migrator(single pass: export + import in-memory) - Destinations & Keystores —
neo-destinations-keystores-migrator(deferred — run after all CF apps are deployed) - Roles —
subaccount-roles-export→subaccount-roles-import
After all skills complete, it produces a consolidated report summarizing results across all domains.
Individual skills can be invoked standalone. If you only need to migrate destinations and keystores, invoke
neo-destinations-keystores-migratordirectly — you do not need the orchestrator.
Orchestration Algorithm
This orchestrator follows the same Orchestrator-Worker pattern as neo-to-cf-migration-orchestrator. Each migration skill is dispatched to a general-purpose subagent so the orchestrator's context never holds raw trust data, role JSON, or shell output from the underlying scripts.
Dispatch tool
Every step (trust migration, roles-export) is invoked via the Agent tool with subagent_type: "general-purpose":
Agent(
subagent_type: "general-purpose",
description: "<step name>",
prompt: <step-specific prompt — see each step below>
)
Inline-only work (collecting inputs, building the consolidated report) stays in the orchestrator. Trust migration produces no disk files — the subagent returns a plain-text summary. Roles export writes $MIGRATION_DIR/neo-roles.json; the orchestrator reads only the small summary fields it needs.
Concurrency policy
| Step | Mode | Why |
|---|---|---|
| Step 0 input collection | Inline | User Q&A and config file writes — no large output. |
| Step 1 trust migration | Single subagent | Export and import run in-memory inside one script; no intermediate files. |
| Step 3 roles export | Sequential (after Step 1) | Roles import is deferred to post-deploy and lives in subaccount-roles-import. |
| Step 4 consolidated report | Inline | Read roles JSON + subagent summaries, render report. |
No fan-out is appropriate here — the steps form a short dependency chain.
Failure & retry policy
- Max retries per step: 2. After 2 failed subagent attempts on the same step, surface to the user.
- Trust migration failure → hard stop. Do not proceed to roles export.
- Roles export failure → log and surface to user. Roles export runs only after trust migration succeeds, but a roles-export failure does not affect trust (they are independent in failure handling).
- Token expiry mid-run → re-spawn the subagent with the same prompt (the trust script and roles export are idempotent; already-existing resources are treated as success).
What this orchestrator does NOT migrate:
- Application source code (use
neo-to-cf-migration-orchestratorfor that)- Keystore and password data (requires cryptographic operations not supported via shell)
- Platform members / cockpit access (use Neo Cloud Cockpit's member export/import)
- Cloud Connector tunnel configuration (SCC Admin UI only)
- HANA data migration (out of scope for this tooling)
Prerequisites
Neo side:
- Neo Platform API OAuth client with scopes:
hcp.readTrustSettings,readDestination,readAuthorizationSettings - Neo subaccount technical name and region host
CF side:
- BTP CLI installed and logged in (
btp login --sso) - CF CLI installed and logged in (
cf login --sso) - CF subaccount GUID (find in BTP cockpit > subaccount Overview)
Tools:
curlon PATHjqon PATH (recommended)
Step 0: Collect All Inputs
Before invoking any skill, resolve all required parameters so the migration can run uninterrupted.
0a. Determine the migration directory:
# If running inside an app migration copy (created by jakarta-java25-migration),
# use its .migration/ folder so all config lives alongside the app.
# Otherwise use a temp directory — subaccount migration is not tied to any single app repo.
if [ -f .migration/cf-migration-config.json ]; then
MIGRATION_DIR="$(pwd)/.migration"
echo "Using app migration directory: $MIGRATION_DIR"
else
MIGRATION_DIR="${TMPDIR:-/tmp}/neo-subaccount-migration"
mkdir -p "$MIGRATION_DIR"
echo "Using temp migration directory: $MIGRATION_DIR"
fi
0b. Check for existing config files:
if [ -f "$MIGRATION_DIR/neo-migration-config.json" ]; then
echo "=== Neo config found ==="
cat "$MIGRATION_DIR/neo-migration-config.json"
fi
if [ -f "$MIGRATION_DIR/cf-migration-config.json" ]; then
echo "=== CF config found ==="
cat "$MIGRATION_DIR/cf-migration-config.json"
fi
0c. Ask the user for any missing values:
Neo subaccount:
- "What is the technical name of your Neo subaccount?"
- "What is the Neo region host?" (e.g.,
eu1.hana.ondemand.com) - "How would you like to authenticate to the Neo APIs? (a) Platform API OAuth client credentials, or (b) pre-issued Bearer token?"
- If (a): client ID and client secret
- If (b): bearer token (warn: 25-minute expiry — may not be sufficient for full migration)
CF subaccount: 4. "What is the GUID of the target CF subaccount?"
0d. Save config files:
Save $MIGRATION_DIR/neo-migration-config.json if it doesn't exist.
Save $MIGRATION_DIR/cf-migration-config.json if it doesn't exist.
0e. If $MIGRATION_DIR is inside a git repo, ensure it is in .gitignore:
# Only relevant when MIGRATION_DIR is inside the app copy (.migration/)
if [[ "$MIGRATION_DIR" != /tmp* ]]; then
if [ -f .gitignore ]; then
grep -q '^\\.migration' .gitignore 2>/dev/null || echo '.migration/' >> .gitignore
else
echo '.migration/' > .gitignore
fi
fi
0f. Verify BTP CLI and CF CLI sessions:
btp target
cf target
If either fails, stop and tell the user which CLI needs login.
0g. Setup NEO API (telemetry consent + functions):
This must run before any subagent is dispatched. Do not proceed to Step 1 until all telemetry files exist.
Resolve telemetry paths:
_NEO_MIGRATION_HOME="${XDG_DATA_HOME:-${APPDATA:-$HOME}}/.neo-migration"
_NEO_CONSENTS_HOME="${XDG_DATA_HOME:-${APPDATA:-$HOME}}/.neo-migration-consents"
_CF_ORG=$(cf target 2>/dev/null | awk '/^org:/{print $2}')
CF_SUBACCOUNT_GUID=$(btp list accounts/subaccount 2>/dev/null | awk -v org="${_CF_ORG}" '
NR > 2 { guid=$1; subdomain=$3; if (index(org, subdomain) > 0) { print guid; exit } }
')
[ -n "${CF_SUBACCOUNT_GUID}" ] || { echo "ERROR: Could not resolve CF_SUBACCOUNT_GUID — check btp login and cf target." >&2; exit 1; }
_SUBACCOUNT_DIR="${_NEO_MIGRATION_HOME}/${SUBACCOUNT}/${CF_SUBACCOUNT_GUID}"
mkdir -p "${_NEO_MIGRATION_HOME}/${SUBACCOUNT}"
mkdir -p "${_SUBACCOUNT_DIR}"
mkdir -p "${_NEO_CONSENTS_HOME}/${SUBACCOUNT}/${CF_SUBACCOUNT_GUID}"
_TELEMETRY_FILE="${_NEO_CONSENTS_HOME}/neo-telemetry-installation.txt"
_CONSENT_FILE="${_NEO_CONSENTS_HOME}/${SUBACCOUNT}/neo-telemetry-consent.txt"
_SESSION_FILE="${_NEO_CONSENTS_HOME}/${SUBACCOUNT}/${CF_SUBACCOUNT_GUID}/neo-telemetry-session.txt"
_SP_SIGNING_CONSENT_FILE="${_NEO_CONSENTS_HOME}/${SUBACCOUNT}/trust-sp-signing-consent.txt"
_KEYSTORES_CONSENT_FILE="${_NEO_CONSENTS_HOME}/${SUBACCOUNT}/dest-keystores-consent.txt"
_OAUTH_CREDENTIALS_CONSENT_FILE="${_NEO_CONSENTS_HOME}/${SUBACCOUNT}/dest-oauth-credentials-consent.txt"
Collect telemetry consent (per subaccount — ask only if not already recorded):
STOP — do not run any bash here. Check whether
$_CONSENT_FILEexists:
- If it exists and contains a valid
consent=line, read the value from there (skip asking the user).- If it does not exist or
consent=is missing, use theAskUserQuestiontool to ask the user: "Enable telemetry? This migration appends two non-PII random UUIDs (neo_cf_migration_installation_id, neo_cf_migration_session_id) as query parameters to every NEO API call for usage tracking. No personal data, subaccount names, or credentials are included. Enable? (yes/no)" Wait for the user's answer before continuing.
if [ ! -f "${_CONSENT_FILE}" ] || ! grep -q "^consent=" "${_CONSENT_FILE}"; then
_CONSENT="<yes or no from user answer above>" # replace with actual value
echo "consent=${_CONSENT}" > "${_CONSENT_FILE}"
fi
_CONSENT=$(grep "^consent=" "${_CONSENT_FILE}" | cut -d= -f2)
Ensure installation_id exists and generate new session_id:
if [ "${_CONSENT}" = "yes" ]; then
# installation_id — once per machine
if [ ! -f "${_TELEMETRY_FILE}" ] || ! grep -q "^neo_cf_migration_installation_id=" "${_TELEMETRY_FILE}"; then
_INSTALLATION_ID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "neo_cf_migration_installation_id=${_INSTALLATION_ID}" > "${_TELEMETRY_FILE}"
echo "INFO: New installation ID generated: ${_INSTALLATION_ID}"
fi
# session_id — ALWAYS new (orchestrator = new migration attempt)
_MIGRATION_ID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "neo_cf_migration_session_id=${_MIGRATION_ID}" > "${_SESSION_FILE}"
echo "INFO: New migration session started. neo_cf_migration_session_id: ${_MIGRATION_ID}"
fi
Collect SP signing key consent (per subaccount — ask only if not already recorded):
STOP — do not run any bash here. Check whether
$_SP_SIGNING_CONSENT_FILEexists:
- If it exists and contains a valid
consent=line, read the value from there (skip asking the user).- If it does not exist or
consent=is missing, use theAskUserQuestiontool to present this disclaimer and ask the user: "The Neo trust configuration includes the signing key of the local service provider (SP). This key flows through the migration script in memory and there is a small risk it may reach this LLM model. Do you want to proceed with the trust migration? (yes/no)" Wait for the user's answer before continuing.
if [ ! -f "${_SP_SIGNING_CONSENT_FILE}" ] || ! grep -q "^consent=" "${_SP_SIGNING_CONSENT_FILE}"; then
_SP_SIGNING_CONSENT="<yes or no from user answer above>" # replace with actual value
echo "consent=${_SP_SIGNING_CONSENT}" > "${_SP_SIGNING_CONSENT_FILE}"
fi
_SP_SIGNING_CONSENT=$(grep "^consent=" "${_SP_SIGNING_CONSENT_FILE}" | cut -d= -f2)
Collect OAuth credentials consent (per subaccount — ask only if not already recorded):
STOP — do not run any bash here. Check whether
$_OAUTH_CREDENTIALS_CONSENT_FILEexists:
- If it exists and contains a valid
consent=line, read the value from there (skip asking the user).- If it does not exist or
consent=is missing, use theAskUserQuestiontool to present this disclaimer and ask the user: "Two destination authentication types (OAuth2SAMLBearerAssertion and OAuth2ClientCredentials with mTLS token retrieval) have credentials that can be extracted from Neo. There is a risk they may reach this LLM model. Do you want to migrate these destinations including their credentials? (yes/no)" Wait for the user's answer before continuing.
if [ ! -f "${_OAUTH_CREDENTIALS_CONSENT_FILE}" ] || ! grep -q "^consent=" "${_OAUTH_CREDENTIALS_CONSENT_FILE}"; then
_OAUTH_CREDENTIALS_CONSENT="<yes or no from user answer above>" # replace with actual value
echo "consent=${_OAUTH_CREDENTIALS_CONSENT}" > "${_OAUTH_CREDENTIALS_CONSENT_FILE}"
fi
_OAUTH_CREDENTIALS_CONSENT=$(grep "^consent=" "${_OAUTH_CREDENTIALS_CONSENT_FILE}" | cut -d= -f2)
Collect keystores consent (per subaccount — ask only if not already recorded):
STOP — do not run any bash here. Check whether
$_KEYSTORES_CONSENT_FILEexists:
- If it exists and contains a valid
consent=line, read the value from there (skip asking the user).- If it does not exist or
consent=is missing, use theAskUserQuestiontool to present this disclaimer and ask the user: "Keystores contain sensitive cryptographic information such as private keys. There is a risk they may reach this LLM model during migration. Do you want to migrate keystores? (yes/no)" Wait for the user's answer before continuing.
if [ ! -f "${_KEYSTORES_CONSENT_FILE}" ] || ! grep -q "^consent=" "${_KEYSTORES_CONSENT_FILE}"; then
_KEYSTORES_CONSENT="<yes or no from user answer above>" # replace with actual value
echo "consent=${_KEYSTORES_CONSENT}" > "${_KEYSTORES_CONSENT_FILE}"
fi
_KEYSTORES_CONSENT=$(grep "^consent=" "${_KEYSTORES_CONSENT_FILE}" | cut -d= -f2)
Source the NEO API setup script:
_NEO_SETUP="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../../shared && pwd)/neo-api-setup-template.sh"
[ -f "${_NEO_SETUP}" ] || _NEO_SETUP="ai-migration/neo-java-migration-skills/shared/neo-api-setup-template.sh"
source "${_NEO_SETUP}" || exit 1
echo "INFO: NEO API setup complete. fetch_neo_*() functions are ready."
Guard — do not dispatch any subagent without all consent files:
[ -f "${_CONSENT_FILE}" ] || {
echo "ERROR: Telemetry consent file not written. Cannot dispatch subagents." >&2
exit 1
}
if [ "${_CONSENT}" = "yes" ]; then
[ -f "${_SESSION_FILE}" ] || {
echo "ERROR: Session file not written. Cannot dispatch subagents." >&2
exit 1
}
fi
[ -f "${_SP_SIGNING_CONSENT_FILE}" ] || {
echo "ERROR: SP signing key consent file not written. Cannot dispatch subagents." >&2
exit 1
}
[ -f "${_OAUTH_CREDENTIALS_CONSENT_FILE}" ] || {
echo "ERROR: OAuth credentials consent file not written. Cannot dispatch subagents." >&2
exit 1
}
[ -f "${_KEYSTORES_CONSENT_FILE}" ] || {
echo "ERROR: Keystores consent file not written. Cannot dispatch subagents." >&2
exit 1
}
Step 1: Trust Migration
Inform the user:
"Step 1/3: Migrating trust configuration (IdP)..."
Dispatch a single subagent via the Agent tool:
Agent(
subagent_type: "general-purpose",
description: "subaccount trust migration",
prompt: <prompt below>
)
Prompt:
You are running the subaccount-trust-migrator skill of the Neo→CF subaccount migration.
Neo subaccount: <NEO_SUBACCOUNT>
Neo region host: <NEO_REGION_HOST>
CF subaccount ID: <CF_SUBACCOUNT_ID>
Your task:
1. Invoke the subaccount-trust-migrator skill and follow it exactly.
- Collect the Neo Bearer token from the user if not already available.
2. Run the migration script. Do NOT write any files.
3. Return a concise report (≤ 15 lines):
- IAS IdPs imported / already_configured / failed counts
- Third-party IdPs skipped (if any)
- Manual steps count
- Status: SUCCESS | FAILED | PARTIAL
Your final message IS the return value. Do NOT return trust configuration data.
If the subagent returns FAILED critically (BTP CLI unavailable, CF subaccount ID wrong, Neo API unreachable), stop and report the error to the user. Do not proceed to Step 3.
Step 2: Destinations and Keystores Migration
Deferred — run AFTER all apps are deployed to CF. App-level destinations and keystores are bound to specific CF app instances. If the CF apps do not exist yet, the migration script cannot create or bind the per-app Destination Service instances.
After completing app code migration (
neo-to-cf-migration-orchestrator) and deploying all apps (cf deploy . -f), invoke skill:neo-destinations-keystores-migrator
Record in the consolidated report that destinations and keystores migration is deferred. When neo-destinations-keystores-migrator is later run in post-deploy Phase 5, its summary output (uploaded/failed counts per account and per app) should be appended to the report manually.
If destinations migration fails, log the error in the consolidated report but continue to Step 3 — roles migration is independent.
Step 3: Roles Export
Inform the user:
"Step 3/3: Exporting application roles and groups..."
Step 3a: Dispatch roles export to a subagent
Invoke the Agent tool:
Agent(
subagent_type: "general-purpose",
description: "subaccount roles export",
prompt: <prompt below>
)
Prompt:
You are running the subaccount-roles-export skill of the Neo→CF subaccount migration.
Subaccount directory: <SUBACCOUNT_DIR>
Neo subaccount config: <MIGRATION_DIR>/neo-migration-config.json
Your task:
1. Invoke the subaccount-roles-export skill and follow it exactly.
2. Write the export JSON to <SUBACCOUNT_DIR>/neo-roles.json.
3. Return a concise report (≤ 20 lines):
- Output file path
- totalApplications, totalRoles, totalGroups
- Status: SUCCESS | FAILED | PARTIAL
- Any auth/token/network errors encountered
Do NOT return the full roles JSON. The orchestrator reads it from disk if needed.
Your final message IS the return value.
Step 3b: Read summary fields inline
jq '.totalApplications, .totalRoles, .totalGroups' "${_SUBACCOUNT_DIR}/neo-roles.json"
Roles import is deferred.
subaccount-roles-importrequires live XSUAAappIdvalues that only exist after apps are deployed to CF. After completing app code migration (Phase 2 inneo-to-cf-migration-orchestrator) and deploying all apps (Phase 4), runsubaccount-roles-importas a final step to link role-templates and assign users. See the Full Subaccount Migration Order section inneo-to-cf-migration-orchestratorfor the complete sequence.
Step 4: Build Consolidated Report
Read all individual reports and build a summary. Save to ${_SUBACCOUNT_DIR}/subaccount-migration-report.json using the Write tool:
{
"subaccountDir": "<_SUBACCOUNT_DIR>",
"sourceSubaccount": "<Neo subaccount name>",
"targetSubaccount": "<CF subaccount GUID>",
"migrationSessionId": "<neo_cf_migration_session_id>",
"migrationTimestamp": "<ISO 8601 timestamp>",
"trust": {
"status": "completed|failed|skipped",
"iasIdPsImported": 0,
"thirdPartyIdPs": 0
},
"destinations": {
"status": "deferred — run neo-destinations-keystores-migrator after all apps are deployed"
},
"roles": {
"exportStatus": "completed",
"importStatus": "deferred — run subaccount-roles-import after all apps are deployed",
"totalApplications": 0,
"totalRoles": 0,
"totalGroups": 0,
"reportFile": "<_SUBACCOUNT_DIR>/neo-roles.json"
},
"requiresManualSteps": true,
"consolidatedManualSteps": [
"<step from trust>",
"<step from destinations>",
"<step from roles>"
]
}
Step 5: Display Consolidated Summary
========================================
Neo Subaccount Migration Complete
========================================
Source: <Neo subaccount> (<region>)
Target: <CF subaccount GUID>
TRUST
IAS IdPs imported: <count>
Third-party IdPs: <count> [manual config required]
Status: <completed|failed>
DESTINATIONS & KEYSTORES
⚠ Deferred: run neo-destinations-keystores-migrator after all apps are deployed
ROLES (export only — import deferred)
Applications: <count>
Roles: <count>
Groups: <count>
⚠ Import pending: run subaccount-roles-import after all apps are deployed
========================================
MANUAL STEPS REQUIRED
========================================
The following items could not be automated and must be completed manually:
TRUST:
1. <trust manual step>
DESTINATIONS:
2. Re-enter password for 'dest1' in CF Destination Service cockpit
3. Re-enter client secret for 'dest2'
ROLES:
4. Run subaccount-roles-import after all apps are deployed to CF
Full report: ${_SUBACCOUNT_DIR}/subaccount-migration-report.json
Individual reports:
Roles export: ${_SUBACCOUNT_DIR}/neo-roles.json
NEXT STEPS:
1. Complete all manual steps listed above
2. Migrate application code: use neo-to-cf-migration-orchestrator for each app
3. Deploy all apps to CF: cf deploy . -f (from each app directory)
4. Run neo-destinations-keystores-migrator to migrate destinations and keystores
5. Run subaccount-roles-import to assign role-templates and users
Configuration Files
| File | Location | Purpose |
|---|---|---|
neo-telemetry-installation.txt |
~/.neo-migration-consents/ |
Machine-level installation ID |
neo-telemetry-consent.txt |
~/.neo-migration-consents/$SUBACCOUNT/ |
Telemetry consent for this subaccount |
neo-telemetry-session.txt |
~/.neo-migration-consents/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/ |
Session ID for the current migration attempt |
trust-sp-signing-consent.txt |
~/.neo-migration-consents/$SUBACCOUNT/ |
SP signing key consent for trust migration |
dest-keystores-consent.txt |
~/.neo-migration-consents/$SUBACCOUNT/ |
Keystores migration consent |
dest-oauth-credentials-consent.txt |
~/.neo-migration-consents/$SUBACCOUNT/ |
OAuth credentials migration consent |
neo-migration-config.json |
$MIGRATION_DIR |
Neo subaccount details and auth credentials |
cf-migration-config.json |
$MIGRATION_DIR |
CF target subaccount details |
subaccount-migration-report.json |
~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/ |
Consolidated migration report |
neo-roles.json |
~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/ |
Roles export output |
neo-roles-import-report.json |
~/.neo-migration/$SUBACCOUNT/$CF_SUBACCOUNT_GUID/ |
Roles import results |
CF Services
| Service | Plan | Purpose | Created by |
|---|---|---|---|
destination |
lite |
Subaccount-level destinations | neo-destinations-keystores-migrator |
Common Issues
Issue: Migration interrupted before completing all steps
Cause: Token expiry, network interruption, or CLI session expiry. Solution: Re-invoke the orchestrator — completed steps are idempotent (already-existing resources are treated as success). The migration will resume from where it failed.
Issue: BTP CLI session expires mid-migration
Cause: BTP CLI sessions have limited validity.
Solution: Run btp login --sso and re-invoke the orchestrator.
Issue: Pre-issued Bearer token expires during export
Cause: Neo API Bearer tokens are valid for only 25 minutes. Solution: Use client credentials authentication (not pre-issued token) for the full orchestrated migration, as it supports automatic token refresh.
Issue: One step fails but others succeed
Cause: Independent failures per domain (e.g., Destination Service not entitled but trust works fine).
Solution: Fix the specific issue and re-invoke only the affected skill directly (e.g., neo-destinations-keystores-migrator). The orchestrator can also be re-run — idempotent operations will skip already-completed work.
Next Steps
After completing subaccount configuration migration:
- Complete all manual steps listed in the consolidated report
- neo-to-cf-migration-orchestrator — migrate application source code and runtime configuration for each Neo application; see its Full Subaccount Migration Order section for the complete multi-app sequence
- Deploy all apps —
cf deploy . -ffrom each app directory (Phase 4 in the full migration order) - subaccount-roles-import — run this after all apps are deployed to assign role-templates and users to the role collections created by
authentication-xsuaa - Test end-to-end — deploy a migrated application and verify authentication, destination connectivity, and authorization using a fresh browser session