Skip to content

Latest commit

 

History

History
542 lines (452 loc) · 31.1 KB

File metadata and controls

542 lines (452 loc) · 31.1 KB

Agent guide — editing a driver set with bin/idm

How an agent (or a person at a shell) changes an IDM driver set without Designer: the tree is the source of truth, the CLI keeps it consistent, the validator is the engine's own verdict, and git is the history.

A human learning the product starts at the repository README and docs/README.md (getting-started.md, day-to-day.md, tree-layout.md). This page is the same loop with the refusal rules spelled out. How to give it to Claude Code, Cursor, or another agent is agents.md.

bin/idm with no arguments prints every command with its arguments. Where this page and that text disagree, follow bin/idm. Edit operations are command <tree> --flag value (for example --form, --prd, --props). --json always means machine-readable output.

The tree

An IDM-as-code tree (model.md) — one file per object:

tree/
  driverset.xml                     manifest: drivers, driver-set linkage, meta
  config-values.xml                 driver-set GCVs
  library/library.xml               Library manifest
  library/<name>.policy.xml         shared policies, mapping tables, GCV objects, ECMAScript
  drivers/<driver>/driver.xml       manifest: config files, icon, artifacts, policy-set linkage
  drivers/<driver>/icon.gif         the driver's Designer icon, if it has one (opaque bytes)
  drivers/<driver>/*.policy.xml     driver-scope policies (schema map, input/output transforms, …)
  drivers/<driver>/subscriber/…     channel policies
  drivers/<driver>/publisher/…
  .package-baseline/…               pre-edit content of customized packaged artifacts

Forms, PRDs, entitlements, and the rest of AppConfig live under the driver as well. The plain-language map, including cases/ and the secrets files beside the tree, is tree-layout.md.

Artifacts are addressed by path: library/<name>, drivers/<driver>/<name>, drivers/<driver>/subscriber/<name>, drivers/<driver>/publisher/<name>. Names are the object names (spaces and all); quote them.

Get a tree from any source:

bin/idm import <driver-or-driverset-export.xml> tree/
bin/idm import-project <designer-project-dir> tree/
bin/idm import-ldif <driverset-subtree.ldif> tree/    # a subtree export from the driver set's DN, objectClass included
bin/idm import-live tree/ --env stg          # connection + driver set from environments.properties (preferred)
IDM_JAVA_OPTS="-Dldap.url=ldaps://host:636 -Dldap.bindDn=… -Dldap.password=…" \
  bin/idm import-live "cn=driverset1,o=system" tree/

Put it in git before editing. The import is byte-idempotent, so re-importing the same source is a no-op diff — which is how you see what changed in a vault. The import writes a .gitattributes (* -text) so git never normalizes line endings: vault content is bytes, and a CRLF inside an ECMAScript resource must survive a checkout or vault.diff will report it. Keep that file.

Orient first

bin/idm validate tree/                       # the baseline: is this tree sound?
bin/idm query tree/ artifacts "AD Driver"    # what the driver has
bin/idm query tree/ chain "AD Driver" sub    # the subscriber chain in execution order
bin/idm query tree/ gcvs "AD Driver"         # every GCV in scope, value, and where it's defined
bin/idm query tree/ tables "AD Driver"       # mapping tables in reach, with columns
bin/idm show tree/ "drivers/AD Driver/subscriber/sub-ctp-Transform"
bin/idm refs tree/ "library/lib-Shared"      # who links / includes / maps it
bin/idm query tree/ drivers                  # driver names and directories
bin/idm query tree/ fishbone "AD Driver"     # the policy-flow fishbone (--json for the VS Code viewer)

validate on a tree from a running vault should report 0 errors. If it doesn't, the errors are real (a dangling reference, a GCV nothing defines) — or a validator bug, which is the same as a finding: report it, don't work around it.

Provisioning: forms and PRDs

A User Application driver's cn=AppConfig subtree (JSON/Form.io provisioning forms and their request definitions — see forms.md) reads and writes with import/import-project/import-ldif/import-live like any other driver content, under drivers/<driver>/provisioning/.

bin/idm form.list tree/ --driver "User Application Driver"           # kind, name, title, #fields, packaged mark
bin/idm form.show tree/ "Help-desk Request Form" --json               # fields, scripts, languages, PRDs that bind it
bin/idm prd.list  tree/ --driver "User Application Driver"            # status, category, json-forms/classic, bound forms
bin/idm prd.show  tree/ HelpdeskTicket                                 # properties, bindings, workflow activities

form.show accepts a bare name (searched across every driver's provisioning), <kind>/<name> (request/approval/template), or <driver>/<kind>/<name> when names collide. A form is referenced by name from a PRD's form-binding; form.show/prd.show cross-reference the two so you can see a field's shape and everywhere it's used in one place.

Changing a form is a transaction like any other, and every form transaction re-syncs the PRD bindings that reference the form (Designer's own rules — the request form's field list is rebuilt from the components; data items are the persisted mappings and are kept or pruned, never invented, so a new field needs an explicit mapping step):

bin/idm form.edit tree/ "Help-desk Request Form" --check                       # which vendor builder would run; one-time fixes if any
bin/idm form.edit tree/ "Help-desk Request Form"                               # a person: opens the vendor form builder; on save+close → stored + bindings synced
bin/idm form.set-content tree/ --form "Help-desk Request Form" --content-file new.json   # an agent: same result without a GUI
bin/idm form.sync tree/ --form "Help-desk Request Form"                         # after a builder saved into the tree with --no-wait

The form is stored pretty-printed (readable diffs); the deployer and the Designer writer emit the compact form the vendor tools use. Editing one of the 11 stock forms or a stock PRD is allowed: it is marked customized and its pre-edit document is baselined under .package-baseline/, exactly like a packaged policy.

For a change an agent can make without the GUI, use the typed operations (form.add, form.field.add/set/remove/move, form.set, form.localize, form.rename, form.delete, prd.map, prd.add, prd.delete) — same transaction machinery (--dry-run, --force, --json, validate after every write). A common recipe, add a field to a request form and map it to flowdata:

bin/idm form.field.add tree/ --form "Help-desk Request Form" --key priority --type select \
    --label Priority --required --props '{"data":{"values":[{"label":"High","value":"high"},{"label":"Low","value":"low"}]}}'
bin/idm prd.map tree/ --prd HelpdeskTicket --field priority    # default target: flowdata.Start/Help-desk_Request_Form/priority
bin/idm validate tree/                                          # 0 errors expected (FormCheck runs by default)

form.field.add places the field at the end of components by default, or next to another field (--after/--before/--first) or inside a named container/columns component (--in); it starts from the captured builder template for that type when one exists (resources/forms/components/) so a form we author round-trips through the vendor builder unchanged, or the minimal {label,key,type,input} shape with --minimal (what the IDM 4.10.1 stock forms actually use). prd.map is the one binding-sync never does on its own — a new field starts unmapped until the agent explicitly maps it (or maps an approval activity's data item with --activity); everything else about keeping the PRD in step (rebuilding the request field list, pruning a mapping whose field disappeared) happens automatically on every form-changing operation. FormCheck (part of the standard validator) flags a document with no components, a duplicate or missing key, an unknown component type, a conditional/logic reference to a key that doesn't exist, a script that doesn't compile, a request form with no button, incomplete localization, and a PRD binding/mapping that's stale, drifted, or unbound.

To look at a form before deploying it, without the Identity Applications:

bin/idm form.preview tree/ "Help-desk Request Form" --out helpdesk.html   # self-contained page: open-source Form.io renderer + placeholders for NetIQ components

It is a layout and conditional-logic check (live data sources are shown as "not fetched"), not the vendor renderer. Deploying forms and PRDs is the normal vault.diff → vault.deploy → verify path; no driver restarts, and the plan says when the Identity Applications may need a cache flush.

Reading a workflow (Track W step W1)

A PRD's <process> — the workflow the Identity Applications actually runs — reads as a typed view without hand-parsing XML:

bin/idm prd.flow tree/ HelpdeskTicket                                  # header + a walk of the graph from start, activity by activity
bin/idm prd.flow tree/ HelpdeskTicket --format mermaid --out flow.mmd  # a flowchart another tool (or a person) can render

The text form walks the activity graph breadth-first from start-activity following links (an activity no link ever reaches — a broken process — is still listed, tagged (unreachable)), printing each activity's kind, display name, the attributes that matter for its kind (timeout/approver-type/addressee for an approval, the expression for a condition, category/operation for a provisioning step, …) and its outgoing links, then every activity's data items. --format mermaid emits a flowchart TD instead. Neither needs the Identity Applications, Designer, or a live vault.

validate (on by default, FlowCheck) mirrors the workflow engine's own ten load-time checks (ModelFactory.loadProcessFlow / ProcessFlowModel.validate() — see docs/workflows.md §1.2) against every PRD's <process>: known process version, exactly one start/finish, every link's endpoints and type valid for its source activity's kind, a condition has both a true and false link, no dangling activity, a branch has its merge, RBAC/RBACSOD/Resource processes bind both outcomes, every flowdata. reference is .get(/.getObject(, every approval has an addressee, notify/confirm/reminder have a template — plus checks the engine doesn't run at all but that catch a broken workflow before deploy: attribute enums, an addressee/expression/data-item source that doesn't compile as ECMAScript, a leftover {enter … here} template placeholder (warning on an Active PRD, informational on a template), and an activity with no display name. docs/workflows.md §1.4 has the full rationale; commands.md lists every flow-* code.

Author a workflow (Track W steps W2/W3)

Changing the flow itself — not just a form's fields — is the flow.* typed operations (commands.md has the full list): one op per concept (flow.activity.add/set/rename/remove, flow.branch.add/remove, flow.link.add/remove/retype, flow.data.set/remove, flow.set), each a transaction like every other edit here (--dry-run, --force, --json, validate after every write — a change FlowCheck would newly flag is refused before it is written, same as prd.map/form.field.add). A common recipe — start from NoApproval (the simplest stock template: start → grant an entitlement → finish), replace its provisioning step with a condition and a two-step approval, then look at the result:

bin/idm prd.add tree/ --name "Widget Access" --from-template NoApproval --request-form "Widget Request Form"
bin/idm flow.activity.remove tree/ --prd "Widget Access" --id prov          # drop the stock grant step
bin/idm flow.activity.add tree/ --prd "Widget Access" --kind condition --id needs_reason \
    --after Start --expression "flowdata.get('reason') != null"
bin/idm flow.activity.add tree/ --prd "Widget Access" --kind approval --id approval_1 --after needs_reason --via true \
    --addressee "IDVault.get(recipient,'user','manager')"
bin/idm flow.activity.add tree/ --prd "Widget Access" --kind approval --id approval_2 --after approval_1 --via approved \
    --addressee "'cn=uaadmin,ou=sa,o=data'"
bin/idm prd.map tree/ --prd "Widget Access" --field reason                    # bind the request form's field to flowdata
bin/idm validate tree/                                                       # 0 errors, 0 flow-placeholder expected
bin/idm prd.flow tree/ "Widget Access" --format mermaid --out flow.mmd       # review the shape before deploying

flow.activity.add --after Y inserts the new activity into Y's single outgoing link (or the one named by --via, when Y has more than one — the op refuses and names the choices otherwise) and wires the new activity's own default outgoing link(s) for its kind (an approval's approved/denied, a condition's true/false, everything else's forward) — so the graph is always dangling-free and validates clean right after the op, without a separate flow.link.add. Parallel work is flow.branch.add (a branch/merge pair) followed by flow.activity.add --after <branch> --to <merge> for each leg. An approval's stock shape (timeout, default addressee, notify template+maps, retry) and a provision activity's five entitlement data items come from the same idm254 templates prd.add --from-template copies from (TemplateSingleApproval_TD, NoApproval) — see commands.md. Once the flow reads right, deploy it the normal way: vault.diff → vault.deploy.

flow.activity.add's four other kinds — rest, role-request, resource-request, start-flow — call out to REST, role/resource requests and other PRDs, and were calibrated (docs/workflows.md §4 W3) against the engine's JAXB binding classes alone (no stock PRD uses any of them, so there is no example to copy a shape from). Add one the same way as any other activity, kind-specific flags after --kind:

bin/idm flow.activity.add tree/ --prd "Widget Access" --kind rest --id lookup --after Start \
    --protocol https --host api.example.com --port 443 --path /v1/widgets --method GET \
    --header "Authorization=Bearer TOKEN" --status-to statusCode --content-to body
bin/idm flow.activity.add tree/ --prd "Widget Access" --kind role-request --id grant_role --after lookup \
    --role "'cn=Widget Users,ou=roles,o=data'" --target recipient --description "'Grant the Widget Users role'"
bin/idm flow.activity.add tree/ --prd "Widget Access" --kind start-flow --id follow_up --after grant_role \
    --process "'Widget Follow-up'" --recipient recipient
bin/idm validate tree/          # flow-start-flow-unknown warns here: no PRD named "Widget Follow-up" exists yet

flow.activity.set takes the same kind-specific flags to update an existing activity of that kind (a flag that doesn't match the activity's kind is refused, naming it); a repeatable one (--header/--role/--target/ --param/--recipient) replaces the whole list when given. These four kinds have no live proof yet — trying one for real needs a REST endpoint or a role on the lab (docs/workflows.md §4 W3).

Entitlements (Track W step W4b)

A provision activity's DirXML-Entitlement-DN (above) has to name something real for a grant to do anything — docs/entitlements.md covers the model. An entitlement is a DirXML-Entitlement object hanging directly off the driver (cn=<name>,cn=<driver>,<driver set>), not under AppConfig, so it lives at drivers/<driver>/entitlements/<name>.xml in the tree and rides the normal vault.diff/vault.deploy (no driver restart — the Identity Applications read it from the vault at grant time):

bin/idm entitlement.add tree/ --driver Loopback --name TestAccess --display-name Group --multi-valued --values a,b
bin/idm flow.activity.add tree/ --prd "Widget Access" --kind provision --id prov --after approval_2 --via approved \
    --entitlement-dn "cn=TestAccess,cn=Loopback,cn=driverset1,o=system"
bin/idm validate tree/                                                       # flow-entitlement-unknown/-external if the DN doesn't resolve

entitlement.remove refuses while any PRD's provision activity still names it (--force never overrides that — repoint or remove the activity first). entitlement.list/entitlement.show orient; EntitlementCheck validates the document itself (root element, conflict-resolution, multi-valued); FlowCheck cross-checks a provision activity's DN against the tree's drivers. See commands.md for the full flag list.

The rest of AppConfig (roles, resources, entities, nav items …)

Everything else under the User Application driver's AppConfig is in the tree as provisioning/objects/<path>.xml (docs/appconfig.md). Read with appconfig.list / appconfig.show; edit with the typed operations:

bin/idm role.add tree/ --name Auditor --level 20 --category Custom --display "Auditor" --display "de=Prüfer" --descr "Audits access"
bin/idm resource.add tree/ --name AuditGroup --category Custom --entitlement "cn=Groups,cn=AD,cn=driverset1,o=system" --param "cn=auditors,ou=groups,o=data"
bin/idm entity.attr.add tree/ --entity user --key roomNumber --ldap roomNumber --display "Room" --required true
bin/idm appconfig.set tree/ --path UIConfig/NavItems/AccessRptTool --attr nrfLocalizedNames --value "de=Zugriffsbericht"
bin/idm role.remove tree/ --name Auditor

appconfig.add/set/remove work on any object; the guards are the vault's: runtime containers are never touched, an object that holds objects or is named by another's DN is not removed, a packaged one needs --force, and the operational attributes (equivalentToMe, DirXML-Associations) are refused. A packaged object's first edit gets a baseline and the customized mark, as a form's does; package.revert tree/ --path <tree path> puts any customized packaged thing back (content, mark and the package's checksum). Deploy as always: vault.diff shows the objects, vault.deploy writes only the changed attributes.

When a person wants to read a trace rather than have it summarised, open the desktop viewer for them: driver.trace view --env <env> --driver <D> streams live in the DirXML Trace Viewer, driver.trace view --file <trace> opens a file (viewer.install first, once; viewer.check says whether it is there). It is read-only and separate from what you read yourself with tail.

To make a driver fully custom — no package ever installed it, as far as the tree, the vault and Designer can tell — package.strip tree/ --driver D removes every stamp, mark and baseline under the driver and marks it stripped; the next vault.deploy then deletes the vault's stamps and package aux classes too (docs/packages.md §3.7). Library items are shared, so they need --library.

Two kinds of change

Content — the rules inside a policy, a stylesheet, a script, a table's rows: edit the file. DirXML Script is the language; the engine's compiler judges it:

$EDITOR "tree/drivers/AD Driver/subscriber/sub-ctp-Transform.policy.xml"
bin/idm validate tree/

Structure — anything that touches more than one file or the linkage: use an operation. Each loads the tree, applies the change, validates, and writes only if it introduced no new error; otherwise it refuses and writes nothing.

bin/idm policy.add tree/ --driver "AD Driver" --scope subscriber --name sub-ctp-NormalizeTitle \
    --link subscriber-command --at "after:drivers/AD Driver/subscriber/sub-ctp-Transform"
bin/idm rule.add tree/ --path "drivers/AD Driver/subscriber/sub-ctp-NormalizeTitle" \
    --content-file rule.xml --at last
bin/idm artifact.rename tree/ --path "library/LocCodeMap" --name LocationCodeMap
bin/idm gcv.set tree/ --driver "AD Driver" --name drv.user.container --value "data\users"
bin/idm filter.set-attr tree/ --driver "AD Driver" --class User --attr Title --publisher sync --subscriber sync
bin/idm mapping-table.set-row tree/ --path library/LocationCodeMap --col LocCode=0042 --col Domain-Placement="OU=x,DC=y"

Every operation takes --dry-run (do everything but write; the result shows what would change), --force (write despite new errors — say why in the commit), and --json (the same result as data: ok, written, touched, renamed, customized, changedFiles, deletedFiles, newErrors, and the full validate report).

Read a refusal as information, not an obstacle:

refusal it means
'…' is still referenced — link from drivers/X (set …); include from … delete would leave a dangling reference; --unlink drops policy-set links, but an <include> or a Map token is content — fix that policy first
would introduce N validation error(s) the engine would refuse to load the result; the listed errors are its diagnostics
GCV 'x' is not defined … add --define <type> you're setting a GCV nothing defines; create it deliberately
GCV 'x' is read by … policies deleting it would stop the driver from starting
'x' names 2 rules; use #n duplicate descriptions — address the rule by position

Packaged content

Overriding a policy that came from a package is normal. The first operation that touches one keeps its pre-edit content in .package-baseline/ and marks it package.customized in the manifest; the result says customized (packaged; baseline kept). Later:

bin/idm package.diff tree/ "drivers/AD Driver/subscriber/NOVLADDCFG-sub-ctp-EntitlementsImpl"

shows exactly what was customized. Commit the baseline with the change. (The vault-side "modified" mark is set by the deployer when it writes the object.)

Add a driver

New drivers are authored in the tree, not in Designer. Three sources:

bin/idm driver.add tree/ --name "AD Driver 2" --from-export ad-export.xml   # a vendor/package export (driver-set form keeps Library scope)
bin/idm driver.add tree/ --name "AD Driver TEST" --copy-of "AD Driver"      # a deep copy: artifacts, config and links re-pointed
bin/idm driver.add tree/ --name Loop --shim-class com.example.Shim         # blank: channels, empty filter, no policies

The operation is a transaction like the others: the new driver must validate in this tree, so an export whose GCVs are defined at the driver-set level of its origin will be refused until those GCVs exist here (define them first with gcv.set --define). Deploy it like any other change; the deployer creates the objects and refuses until the driver's required secrets are in the environment's secrets file (or --allow-missing-secrets).

Packages

Packages are Designer's unit of vendor and custom content. DirXMLDev keeps its own catalog (a git directory of package jars) and installs, inspects and builds packages without Designer (packages.md):

bin/idm package.fetch  --catalog ~/idm-packages --short NOVLADBASE               # newest version; --all-versions for every one
bin/idm package.import --catalog ~/idm-packages /Applications/Designer/packages/eclipse/plugins   # or a Designer install
bin/idm package.show   --catalog ~/idm-packages NOVLADBASE
bin/idm package.resolve --catalog ~/idm-packages --base NOVLADBASE --feature NOVLADDCFG
bin/idm driver.add tree/ --name "AD Driver" --catalog ~/idm-packages --package NOVLADBASE,NOVLADDCFG --answers ad.properties
bin/idm package.install tree/ --catalog ~/idm-packages --package NOVLADENTEX --driver "AD Driver" --answers ad.properties
bin/idm package.status tree/ --catalog ~/idm-packages          # installed, customized, newer versions
bin/idm package.build  --catalog ~/idm-packages tree/ --driver "AD Driver" --short PBTADCUST --name "AD customizations" --vendor pointbluetech
bin/idm package.site   --catalog ~/idm-packages --out /var/www/packages   # an update site Designer users can add

An install is a transaction like any other edit, and reproduces Designer's install exactly (prompts, placement, weights, stamps); the deployer writes the package stamps to the vault, so Designer sees the packages when it imports. A refusal naming mandatory prompts lists what the answers file needs. Content that references GCVs another package defines installs in the same transaction as that package (--package a,b,c). A tree imported before package stamps were read needs package.adopt (or a fresh import-live).

Prove the change

bin/idm validate tree/                                   # engine will load it
bin/idm simulate tree/ --cases cases/ --against /path/to/tree-before # another as-code tree, not a git rev
git add -A tree/ && git commit -m "AD: normalize Title on subscriber command (#123)"

simulate runs the client's regression cases (harvested with the simulator's harvest, or authored) against the edited tree and diffs them against the previous tree — an output that changed is shown, never hidden.

Deploy it

Vault targets live in a gitignored environments.properties (path in IDM_ENVIRONMENTS; see vault-deploy.md); secrets the tree can't carry in a gitignored secrets file per environment.

bin/idm vault.diff   tree/ --env stg                      # what differs, per object; exit 1 if anything
bin/idm vault.deploy tree/ --env stg --driver "AD Driver" --dry-run   # the plan, nothing written
bin/idm vault.deploy tree/ --env stg --driver "AD Driver" --yes       # snapshot → write → restart → verify → audit
bin/idm vault.deploy tree/ --env stg --step               # or: confirm and verify each change
bin/idm vault.verify tree/ --env stg                      # re-read: vault == tree
bin/idm vault.rollback --env stg --snapshot deploy-snapshots/stg/<ts>.ldif --yes

What the deployer will not do: write anything the tree doesn't validate clean; delete a driver (it reports one the tree lacks); restart a stopped driver (it loads the new configuration when started); touch a secret unless the driver is new or you pass --secrets all|missing; deploy to a prd tier without --confirm <env>, a committed tree, and a vault that matches the last recorded deploy (or --capture-drift, which records the vault's current state on an as-found/<env>/<ts> branch for you to merge first). Every deploy appends to deploy-log/<env>.jsonl — commit it with the tree.

Operate it

bin/idm driverset.status --env stg                          # every driver: state, start option, cache bytes, trace
bin/idm driver.status    --env stg --driver "AD Driver"     # + trace file, named passwords, recent audit
bin/idm engine.stats     --env stg --driver "AD Driver"     # JVM heap/threads + cache and operation counters
bin/idm driver.cache view --env stg --driver "AD Driver" --out cases/ad-cache   # queued events → a simulator case
bin/idm driver.trace tail --env stg --driver "AD Driver" --since 10 --grep "Applying rule"
bin/idm driver.restart --env stg --driver "AD Driver" --yes
bin/idm driver.stop --env stg --driver "AD Driver" --yes
bin/idm driver.trace set --env stg --driver "AD Driver" --level 3 --file /var/opt/novell/eDirectory/log/ad.trace --yes
bin/idm driver.submit --env stg --driver "AD Driver" --xds event.xds --yes --tree tree/   # the canary
bin/idm driver.cache clear --env stg --driver "AD Driver" --yes --confirm stg

Gating follows what an operation can break (operate.md): reads (status, cache view, trace show/tail, secrets list, engine.*) are free; start, restart, trace set/reset, and secrets set/remove need --yes on stg and --yes --confirm <env> on prd; stop, resync, migrate, and submit need --yes on every tier and --confirm <env> on prd; cache clear prints the count and first/last event, saves the events to deploy-snapshots/, and needs --yes plus --confirm <env> on stg and prd. Every state change is audited. driver.submit --tree is the ground truth for a policy change: the live engine's Subscriber channel hands the shim a document, the trace shows which, and the simulator's prediction from the tree must match it.

Hand it back to Designer

Teams that keep a Designer project get it updated from the tree, not re-imported:

bin/idm export-project tree/ ~/designer_workspace/Client --dry-run   # what it would touch
bin/idm export-project tree/ ~/designer_workspace/Client             # writes; result lists created/changed/deleted files
bin/idm docs tree/ --out docs/ --since HEAD~5                        # README, one page per driver, library, changes

The writer edits only the files the diff calls for: a changed policy rewrites its _contents.xml; an added one gets a new 8-character id, its CObject and contents files, and a Child relation on its owner; a removed one loses its files and every relation that pointed at it; a rename keeps the id; linkage rewrites the ordered relations on the driver or channel. Everything else in the project is byte-identical afterwards. It refuses a driver that carries package metadata (Designer's catalog owns those; deploy to the vault and import there, or write a whole new project with --new below) and never deletes a driver. Commit the project after it runs and open it in Designer once before trusting a new kind of change.

A project the team doesn't have yet — --new

When there is no Designer project (the tree came from import-live, or the team wants one without pointing Designer at the vault), write a whole one:

bin/idm export-project tree/ ~/designer_workspace/Client --new --dry-run
bin/idm export-project tree/ ~/designer_workspace/Client --new \
    --vault-name IDM_TREE --vault-host vault.example.com --vault-user cn=admin,ou=sa,o=system \
    --server idm-engine --server-context ou=servers,o=system

The directory must not exist, or be empty — its basename is the project name, and it is written into .project, <name>.proj and <name>.cproj (a mismatch is what gives Designer "No valid .proj file" or a silent, empty import, so never rename a project folder by hand afterwards). Everything the tree holds is written: the driver set, the Library, every driver with its channels, filter, policies, resources and GCVs, each driver's Application_ and modeler node, the User Application driver's whole AppConfig (forms and PRDs) and its entitlements. The vault and server flags are all optional, and no vault password is ever written (IdentityVaultSavePassword=false) — Designer asks for it on the first connect.

Packaged items are written with their package attributes and an <id>_initial_state.xml baseline, but the project's package catalog (IdmPackage_ objects, Idm:InstalledPackages relations) is milestone N3: the result note lists the packages the tree names, and until the catalog exists Designer treats those items as plain. --new never touches an existing project; drop the flag to update one.

Conventions

  • One operation or one content edit per commit, with the result's file list in the message; --json output is fine to paste.
  • Never hand-edit driver.xml / library.xml / driverset.xml manifests — that's what the operations are for.
  • Never delete .package-baseline/ entries; they're the record of every override.
  • Don't run two operations on the same tree concurrently.
  • Client trees hold client policy: they live in the client's repo, never here.