Skip to content

connect(servicenow): align /connect ServiceNow with SNOW V2 spec - #217

Draft
mukesh4139 wants to merge 46 commits into
mainfrom
users/mukesh4139/servicenow-connect-adk
Draft

connect(servicenow): align /connect ServiceNow with SNOW V2 spec#217
mukesh4139 wants to merge 46 commits into
mainfrom
users/mukesh4139/servicenow-connect-adk

Conversation

@mukesh4139

@mukesh4139 mukesh4139 commented Jul 25, 2026

Copy link
Copy Markdown

What & why

Migrates /connect servicenow from the legacy monolithic connect/servicenow/stepN
flow to the Workday-style setup orchestrator (pattern from #181). ServiceNow was
the last connect skill on the old monolith. Beyond the port, this PR closes the SNOW
V2 spec gaps, adds programmatic FlightCheck verification for every automatable step,
and makes the flow per-product (HRSD + ITSM) and resume-safe.

  • Orchestrator spinesetup/servicenow/tasks.md (canonical checklist with
    per-row metadata) + setup/servicenow/SKILL.md, a resume-aware Message-block router.
  • 7 setup playbooks — environment, ESS install, connection basics, the two
    mutually exclusive Entra sign-in paths (user + certificate), extension pack +
    connection, and validate/handoff.
  • Monolith retired — old connect/servicenow/ step files deleted; step1.md +
    connect/SKILL.md delegate to the orchestrator (drops legacy oauth2/basic/graph paths).

Limitations in the legacy setup this PR resolves

The monolith had no durable state model or programmatic verification, so several
failure modes could silently pass or wedge the flow:

  • No install evidence — pack install was assumed done; a partial/failed install
    was invisible. → deterministic SN-PKG-001 per product.
  • Connection health caught late — a missing/unhealthy ServiceNow or Dataverse
    connection surfaced only at bind/flow time. → up-front SN-CONN-OBJECTS-001 gate.
  • Single-product only — HRSD and ITSM shared one flat state; adding a second
    product after completion wasn't a supported transition and corrupted state. →
    per-product productStatus + explicit add-product transition (HR→Both, IT→Both).
  • Flows misclassified — ITSM flows were labelled HRSD (host-prefix heuristic), so
    per-product completion rested on wrong evidence. → classify by explicit product token.
  • Attestation could override reality — a Failed portal check and a proven-absent
    role check could both be completed by user attestation. → failed programmatic gates
    now stay blocked; attestation only when evidence is genuinely unavailable.
  • Portal URL only checked for presence — any absolute URL passed. → compare the
    stored value against the maker-confirmed URL per product.
  • Excess maker round-trips — flows/connect/share always prompted for manual work.
    → verify first, prompt only on real gaps; bundle connect+share; reuse share state in S7.1.

New programmatic verification introduced by this PR

Every automatable setup step is now backed by a --checkpoint-runnable FlightCheck
(read-only; the agent never mutates Entra/ServiceNow). Net-new checkpoints:

  • SN-CONN-OBJECTS-001 (S6.0) · SN-PKG-001 (S6.1) · SN-DV-CONN-001 (S6.2) ·
    SN-BASEURL-001 (S6.6) · SN-CONFIG-001/SN-PERM-001/SN-USER-001 (S3.x) ·
    SN-ENTRA-SCOPE-001/SN-ENTRA-CONSENT-001/SN-ENTRA-CERT-001 (S4–S5) ·
    SN-FLOWCONN-001.

All registered (drift-protected via OWNED_PREFIXES) and wired into the
servicenow / servicenowentra CLI scopes. Custom ServiceNow app registrations
degrade to SKIPPED (never a silent pass) when not yet provisioned.

Checkpoints and what each verifies

Checkpoint Step Verifies (short)
SN-CONFIG-001 S3.1 Instance URL + product scope captured
SN-PERM-001 S3.2 Maker Power Platform permissions present
SN-USER-001 S3.3 ServiceNow user record exists
SN-ENTRA-SCOPE-001 S4.1 Sign-in app scope + connector pre-auth
SN-ENTRA-CONSENT-001 S4.2 Tenant-wide admin consent granted
SN-ENTRA-CERT-001 S5 Service-account certificate valid, unexpired
SN-CONN-OBJECTS-001 S6.0 ServiceNow + Dataverse connections connected
SN-PKG-001 S6.1 Extension pack(s) installed per product
SN-DV-CONN-001 S6.2 Pack's Dataverse reference bound + active
SN-FLOW-* S6.3 Product's cloud flows enabled
SN-FLOWCONN-001 S6.4 Flow invoker connection bound
SN-BASEURL-001 S6.6 Portal Base URL matches confirmed value

Tests

Router wiring + monolith-removed guards; per-checkpoint suites (pack install, portal
confirmed-value, flow classification, connection objects, Entra apps). Full FlightCheck
suite green.

Deferred

ServiceNow-internal OIDC provider, system user, and claim mapping stay attestation
rows (no queryable API) and fall back cleanly, so the flow is spec-correct today.

…c config schema

G1: gate oauth2/basic/graph behind SNOW_ALLOW_LEGACY (out of V2 scope, retained as legacy). G2: authType vocabulary -> entra_user/entra_certificate. G11: config.json reshaped to spec v2 (scope, portalBaseUrl, makerPermissions, entra, connections, stepStatus). There is no spoon - only two auth methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…e (G3, G4)

G3: new step0-prerequisites.md gates the flow on ENV-001/002, ENV-CAPACITY-001, ESS-SOLN-001 (reused existing checkpoints, mirrors Workday setup); hands off to /setup on failure, re-runs on resume. Dispatcher now enters ServiceNow via step0. G4: step1 1.3b probes Entra admin (Graph role check) and asks the ServiceNow-admin question, persisting makerPermissions. Also finished G2 rename in the connect dispatcher. Know thyself - and thy permissions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…ic handoff (G7, G14, G8, G12)

New step5-portal-and-validate.md implements the missing spec Steps 5c/5b/5e/6: G7 sets ServiceNowPortalBaseURI on both packs (SN-BASEURL-001 or attest, + reset-after-update warning); G14 OBO connection-parameter sharing (attest, no auto-validation); G8 drives the live 'Show my HR cases'/'Show my IT tickets' prompts with 401/403/empty/broken-link routing (the true E2E proof); G12 hands topic creation to the topics/create skill. step4 now chains into step5 instead of stopping. Follow the white rabbit to a working link.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…user attest (G9, G10, G13, G15)

G9: admin consent promoted to a discrete step 2.2b (SN-ENTRA-CONSENT-001 + escalation to a consent-capable admin). G10: new 2.5b adds the SNI trustedCertificateSubjects manifest requirement for the cert path (SN-ENTRA-CERT-001). G13: security_admin elevation reminder before ServiceNow OIDC work in both auth paths (spec 1.7). G15: SN-SYSUSER-001 attestation + oid->User ID mapping enforced on the cert system user. G5 (manual/attest default vs MCP accelerator) framed inline per ADR 0001 - full MCP rework flagged for review. Wired SN-* checkpoint references into the steps. What is real? The attestation you can verify.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
@mukesh4139

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Microsoft"

mukesh4139 and others added 25 commits July 26, 2026 13:15
…spine

Free your mind: ServiceNow follows Neo out of the connect/stepN Matrix and
into the setup-orchestrator form Workday already lives in.

- setup/servicenow/tasks.md: canonical 7-group checklist (S1-S7) with hidden
  Step/checkpoint/gate metadata, reusing ENV-*/ESS-SOLN-001 for groups 1-2 and
  mutually-exclusive auth-path groups 4 (entra_user) / 5 (entra_certificate).
- setup/servicenow/SKILL.md: resume-aware router mirroring setup/SKILL.md, with
  ServiceNow-specific auth-path-variant rendering and attest-only ServiceNow OIDC.

Playbooks, FlightCheck servicenow.py, and the connect redirect land in follow-ups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…nect redirect

Complete the ServiceNow /connect migration to the Workday-style setup
orchestrator. There is no spoon-fed step sequence anymore — /connect
servicenow now delegates to the resume-aware orchestrator.

- Add the 7 ServiceNow setup playbooks (S1-S7): environment, ESS install,
  connection basics (realizes user-presence spec gap SG-1), the two mutually
  exclusive Entra sign-in paths (entra_user / entra_certificate), extension
  pack + connection, and validate/handoff. ServiceNow-internal OIDC rows
  (S4.3/S4.4/S5.3) stay attest-only: the agent never automates them.
- Redirect connect/step1.md + connect/SKILL.md ServiceNow branch to the
  orchestrator; legacy connect/servicenow/step*.md retained behind ALLOW_LEGACY.
- Update test_setup_router.py for the new ServiceNow routing and add
  test_servicenow_router.py to pin the orchestrator wiring against drift.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
There is no going back to the old flow. Now that /connect servicenow delegates
entirely to the setup orchestrator, the connect/servicenow/stepN files were an
orphaned island — nothing outside the folder routed into them. Delete all 14,
matching how the Workday migration removed connect/workday/.

- Drops the legacy oauth2/basic/graph auth paths (out of V2 scope). Only
  entra_user and entra_certificate are supported.
- Remove the now-dead ALLOW_LEGACY gate and legacy-playbook handoff from the
  orchestrator (capture-servicenow-config, tasks) and the connect docs
  (step1, SKILL); requests for legacy auth now hard-stop with guidance.
- Add test_connect_servicenow_monolith_removed to pin the deletion (parallel
  to the Workday monolith-removed guard).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Free the ServiceNow setup skills 4/5 from their MANUAL-only limbo:
the Entra side of the flow is Microsoft-Graph-only, so it can be
verified programmatically the same way Workday's WD-ENTRA-* checks are.

Adds checks/servicenow_entra.py emitting three checkpoints, each
runnable in isolation via --checkpoint:

  * SN-ENTRA-SCOPE-001  — sign-in app exposes user_impersonation,
    pre-authorizes the Power Platform ServiceNow connector
    (c26b24aa), and requests openid / profile / User.Read.
  * SN-ENTRA-CONSENT-001 — tenant-wide admin consent (AllPrincipals
    oauth2PermissionGrant) covering those three delegated scopes.
  * SN-ENTRA-CERT-001   — the service-account app (App B) holds a
    non-expired AsymmetricX509Cert credential; cross-checks the
    recorded thumbprint when one is captured.

Unlike Workday's gallery app, the ServiceNow apps are custom
registrations with no applicationTemplateId, so resolution is driven
entirely by the identifiers the playbooks persist
(entra.* / certificate.*), degrading to SKIPPED — never a silent pass —
when they are absent. Registers the specs (clients=GRAPH, no Dataverse),
adds SN-ENTRA to OWNED_PREFIXES for drift protection, wires the category
into the CLI (new servicenowentra scope; folded into servicenow + full),
and persists stable certificate.appBObjectId / appBClientId so the cert
check can resolve App B.

Tests: 29 new cases mirroring test_entra_app.py (result+remediation
phrase assertions); full flightcheck suite green (889). Verified live —
SCOPE + CONSENT PASS against the provisioned tenant, CERT SKIPs when the
certificate path is not provisioned.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
The maker who already created the ServiceNow connection during install was
still told to hand-wire it to the extension pack's connection reference in
Copilot Studio. Binding an already-created connection is not an OAuth flow -
it is one Dataverse write of connectionreferences.connectionid. Automate it.

- New scripts/bind_connections.py (ServiceNow-only setup action): finds the
  ServiceNow connection reference, no-ops if already bound, else binds via
  sibling-reuse or BAP connection discovery. 0/1/many active connections
  handled; most-recently-created wins and is reported with its owner so the
  maker can veto. --dry-run and --json supported; best-effort, never crashes.
- Wire P6.3 of install-servicenow-extension-pack.md to run the auto-bind
  before the manual fallback, keying off exit codes (0 bound, 4 create-first,
  3 no-reference, 1 error), then re-verify with SN-CONN-001.
- 14 unit tests covering helpers and every run() branch.

Verified live: correctly resolved the reference and reported already-bound
(no mutation) against the maker's environment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
The base ServiceNow setup binds the Dataverse connection reference
(SN-CONN-001) but never writes the Copilot Studio per-flow invoker
connection, so the agent still surfaces ServiceNow as "Not connected".

Close that gap:
- pp_env_client.py: shared Power Platform env-API client (pac auth) +
  pure helpers for reading/writing flow invoker connection bindings.
- connect_and_share.py: idempotent ServiceNow connect+share action that
  binds the flow invoker connection and shares connection parameters.
- checks/servicenow_flow_binding.py: SN-FLOWCONN-001 verifies the flow
  invoker binding shows Connected (distinct from the Dataverse reference).
- Register SN-FLOWCONN-001 (registry, cli scope map, runner.tenant_id).
- Setup playbook: new P6.3c connect+verify step; write flowBinding state.
- Unit tests for all three modules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Add programmatic flow activation and reorganize the ServiceNow /connect
extension-pack playbook into the order install -> bind -> turn-on-flows ->
connect-and-share -> portal (S6.1 through S6.5).

- bind_connections.py: bind Dataverse refs too; add --connector all to bind
  every unbound reference per connector (reuse-only, never mints connections)
- connect_and_share.py: re-POST a flow invoker binding when the connector is
  not Connected (fixes Stale bindings left after a pack install)
- activate_flows.py (new): headless 'turn on flows' via workflows PATCH
- install_extension_pack.py / pp_env_client.py: programmatic pack install
- playbook/tasks/SKILL docs: renumber S6.x to the new step order
- tests: add coverage for Dataverse binding, stale re-POST, activation, install

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Add an install-mode fork to the ServiceNow /connect playbook. At S6.1 the
maker chooses an automated headless install (via install_extension_pack.py,
the recommended default) or a manual Copilot Studio install. The choice is
persisted to packs.installMode and reused on resume.

All later steps (bind connections, turn on flows, connect and share, portal
URL) stay automated regardless of install mode. When an automated install
leaves no reusable connection, P6.3 already falls back to guided manual
connection creation and then continues automated.

- P6.2: new mode-choice step; automated (P6.2-A) and manual (P6.2-M) branches
- Record S6.1 gate is now prog when automated, manual (with ACK) when manual
- tasks.md / SKILL.md updated to describe the fork

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
The P6.2 install-mode question enumerated its options as a bulleted list in
the Message body, so the agent rendered it as plain chat text and asked the
maker to type a choice instead of invoking the question tool (dropdown).

Rewrite the mode question to match the convention used by the other picker
steps: keep the Message short with the option descriptions as prose, and place
the two selectable labels only in the 'Use the question tool with options ...'
directive. Add an explicit note not to bullet the choices in the message body.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Two fixes to the headless extension-pack installer:

1. Fail-closed scope selection. servicenow_packages() previously treated an
   empty/all-false scope as 'install everything', so an automated run fired
   while scope was unset (or reset) would silently install BOTH the HRSD and
   ITSM packs when the maker requested only one. It now installs only the
   products explicitly selected in config scope; no selection resolves to
   no_targets (exit 4) with a clear message, installing nothing.

2. Heartbeat during the install poll. The install operation is long-running
   and was polled silently, making a multi-minute install indistinguishable
   from a hang (and getting killed prematurely). _poll_install/_install_one
   now emit progress heartbeats to stderr (stdout stays clean JSON), including
   a note that the install continues server-side even if interrupted.

Playbook P6.2-A gains a scope precondition (confirm scope before installing,
verify the dry-run target list) and guidance not to kill a running install but
to re-check SN-PKG-001 instead. Adds fail-closed regression and heartbeat tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
The connect playbook told the agent to run `--checkpoint SN-CONN-001`,
`SN-PKG-001`, `SN-FLOW-*`, and `SN-BASEURL-001`, but none of those are
registered as standalone checkpoint IDs in this workspace (only
SN-FLOWCONN-001, DV-CONN-001, and SN-ENTRA-* are). The unresolved
`--checkpoint SN-CONN-001` call caused the S6.2 bind step to stall with an
"unknown checkpoint" error instead of running the bind.

- S6.2: drop the phantom SN-CONN-001; verify the ServiceNow bind via
  `bind_connections.py` exit 0 + DV-CONN-001, with live health confirmed by
  SN-FLOWCONN-001 at S6.4.
- S6.1/S6.3/S6.5: read SN-PKG-001 / SN-FLOW-* / SN-BASEURL-001 from
  `--scope servicenow --no-open` results (their only emit path).
- Add a global rule: an "unknown checkpoint" message means the ID is
  scope-emitted; switch to the scope run and never treat it as a blocker.
- Harden resume: always re-run the idempotent P6.3 bind, since a pack
  reinstall can silently unbind a reference.

Update tasks.md and SKILL.md metadata to match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…nect chain

Add fire-and-poll modes to install_extension_pack.py (--start returns operation ids without blocking; --status polls one cycle) so the playbook can drive a single self-updating status block across install -> bind -> flows -> connect-and-share -> portal, with up-front expectations. Progress on stderr keeps --json stdout clean. P6.2-A reworked; P6.3-P6.6 advance their own row. 33 installer tests green; live-validated --start.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
The ServiceNow S6.2 step reused the Workday-family DV-CONN-001, which keys
on the Workday pack's hardcoded `..._92b66` Dataverse reference suffix. In a
ServiceNow-only environment that reference is absent, so the check reported
NotConfigured (a false block) with a nonsensical "install Workday" message.

Add SN-DV-CONN-001 in checks/servicenow.py that matches every Dataverse
connection reference by connector (shared_commondataserviceforapps) rather
than a pack-specific suffix, so it correctly validates the ServiceNow pack's
own reference (new_sharedcommondataserviceforapps_*). States: SKIPPED (no
token), NotConfigured (no ref), FAILED (unbound/inactive), PASSED (all
bound+active, owner echoed), WARNING (query error). A self-contained
run_servicenow_dataverse_checks wrapper makes it runnable via --checkpoint;
run_servicenow_checks also calls it for scope runs.

Register the checkpoint, repoint the S6.2 playbook/tasks/SKILL references
from DV-CONN-001 to SN-DV-CONN-001 (keeping a Workday-contrast note), and add
unit tests. Workday's DV-CONN-001 is untouched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
The extension-pack installer derived install targets from `scope` (hrsd/itsm)
read out of the root ESS config `.local/config.json`. But `scope` is
ServiceNow-specific and is written by setup skill 3 (S3.1) into the ServiceNow
connect config `.local/connect/servicenow/config.json` — nothing ever writes
it to the root ESS config. So an automated install (S6.1) following the
documented flow found no scope and failed closed (no_targets, exit 4),
matching what the P6.2-A playbook already states the installer should read.

_load_config now overlays `scope` from the ServiceNow connect config on top of
the root ESS config (root endpoint + agent schema/persona are unchanged; a
root-level scope is honored only as a back-compat fallback). Correct the
no_targets remediation to point at the ServiceNow config file. Add tests for
the overlay, override, back-compat fallback, and missing-config cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Add a real SN-BASEURL-001 flightcheck that verifies the ServiceNow Portal
Base URL is set in the pack template config (Dataverse
msdyn_employeeselfservicetemplateconfigs, key ServiceNowPortalBaseURI).
Previously the playbook referenced this checkpoint but it did not exist in
code. Registers it in the checkpoint registry and adds a self-contained
wrapper so it runs both via the servicenow scope and via
--checkpoint SN-BASEURL-001. Updates P6.6 playbook to reflect the now-real
checkpoint.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Add connect_state.py, a shared merge-only helper that durably records
connect progress into .local/connect/<connector>/config.json. Wire it into
the four ServiceNow action scripts so each records its confirmed factual
artifact (packs/connections/flows/status) plus the setupStatus step it owns
(S6.1-S6.4) on verified success. This keeps a headless or script-first drive
resumable even when the agent's row-recording prose steps are skipped.
Persistence is best-effort and wrapped so it never changes a script exit
code; explicit config merges remain the source of truth for gate evidence.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Add an optional one-line `note` to each setupStatus step so the durable
record can explain what a stage is about. connect_state.record_setup_step
now takes a `note` kwarg (merged in, blank omitted, existing note preserved),
and the four ServiceNow action scripts pass a description for S6.1-S6.4. The
agent-driven path is documented too: checklist-updater.md and config-schema.md
describe the optional `note` key (use the checklist item description).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Add SN-CONFIG-001, SN-PERM-001 and SN-USER-001 — the S3.1/S3.2/S3.3 gates
that capture-servicenow-config.md and tasks.md already drive via
`--checkpoint`, but which were never implemented. A faithful resume into
skill 3 therefore hit "unknown checkpoint" and stalled.

They are config-only (no Dataverse/Graph), read from
.local/connect/servicenow/config.json, and are emitted by a self-contained
run_servicenow_capture_checks so each is independently runnable before any
pack is installed:
- SN-CONFIG-001 (prog): valid instance URL, in-scope product, connector,
  supported sign-in method.
- SN-PERM-001 (prog/manual): ServiceNow admin mandatory (FAIL if absent);
  Entra admin confirmed programmatically PASSES, otherwise MANUAL.
- SN-USER-001 (attest): PASSES once userRecord.activeUserConfirmed is set,
  else MANUAL with the check to run.

Not wired into the flow-gated run_servicenow_checks, so scope-run output is
unchanged. 13 new tests; live-verified against the current env.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Copilot Studio's "share connection parameters" reads its own
`{schema}.<guid>.shared_service-now` connection reference, which it
creates on demand when a maker shares. connect_and_share.py was patching
the solution-shipped `.cr.<short>` reference instead, so the portal kept
showing ServiceNow as "not shared" even though the API/Dataverse state
was fully bound and connected. Reverse-engineered the portal's actual
POST /connectionreferences request to confirm.

Step 3 now mirrors the portal: find the `.shared_service-now` reference
and update it, or create it (POST) when absent. Idempotent and dry-run
aware; verified live against the create path, the update path, and re-run.

Also resolve the connection id dynamically (BAP discovery -> most
recently created Connected ServiceNow connection), consistent with
bind_connections, instead of trusting a reference's stored connectionid
which can go stale. Falls back to a bound reference only when discovery
is unavailable. Exit-4 now means "no active connection found".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…red flows

The connect-and-share step configured `.shared_service-now` references only
for ServiceNow invoker flows registered in the agent's get_user_connections.
When a second extension pack is installed on the same agent (e.g. HRSD
alongside ITSM), its solution-shipped `{schema}.{flowId}.shared_service-now`
reference is bound (connectionid set) but its flow may not yet be registered
on the agent, so the share loop skipped it and connectionparametersetconfig
stayed null — Copilot Studio then showed that pack as "not shared".

Share the union of (a) registered invoker flows and (b) every portal-owned
`.shared_service-now` reference already present in Dataverse. This is
fool-proof against agent-registration staleness and mirrors what the portal
reads. Adds flow_id_from_shared_ref_name helper and covering tests.

Verified live end-to-end on ESS-HR-20260726 (install -> bind -> activate ->
connect&share) for both ITSM and HRSD packs; idempotent on re-run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…eckpoints

The connect-and-share pipeline recorded a single combined S6.4 step only when
BOTH the flow-invoker connect and the parameter share succeeded. If sharing
failed after the connect landed, the run errored (exit 1), nothing was recorded,
and a resume re-ran the whole opaque blob with no visibility into which sub-stage
had failed.

Split them into two independent durable checkpoints so a resume continues from
whichever stage failed:
  - S6.4 — Connect the flow invoker connection
  - S6.5 — Share the connection parameters (NEW)
  - S6.6 — Portal Base URL (renumbered from S6.5)

connect_and_share.py now tolerates a share-write failure without discarding the
confirmed connect: it records S6.4, reports share="error", and exits a new code 6
("connected but sharing failed") so a re-run retries only the share (idempotent
connect is skipped). _persist_connect_state records S6.4 and S6.5 independently
off the result's flow_binding/share fields.

Updates the setup playbook (P6.5 connect+share stages, 6-row live status block,
exit-6 handling), SKILL.md, tasks.md, and the flightcheck S6.x comments to match.
Adds tests for the exit-6 partial-failure path and independent stage recording.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
Add a per-product `productStatus` block to the connect config so a single
agent can install and track both ServiceNow HR (HRSD) and IT (ITSM) side by
side, instead of a single flat `setupStatus` shared across products.

- connect_state: add record_product_setup_step() writing
  productStatus.<product>.<step> via deep-merge (siblings preserved).
- install_extension_pack: record S6.1 install per in-scope product into
  productStatus instead of a single flat setupStatus.S6.1.
- checklist template: split Group 6 into variant sub-blocks 6a ServiceNow HR
  (scope.hrsd), 6b ServiceNow IT (scope.itsm), 6c Shared connection (always),
  mirroring the mutually-exclusive auth groups; product rows tagged product:.
- checklist-updater / capture-config / SKILL.md: route product-tagged rows to
  productStatus, gate product sub-blocks on scope at render time, restore
  productStatus on resume.
- config-schema: document productStatus and the per-product resume model.
- tests: per-product persistence + recorder coverage; update wiring test to the
  new productStatus.<product>.S6.1 location; validate shared-doc Step IDs
  against the union of Workday + ServiceNow templates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…t/share)

Extend per-product ServiceNow setup to the connect chain so a single agent with
incrementally installed HRSD + ITSM packs gets independent checklist
visibility/resume for S6.2-S6.5, mirroring the existing per-product S6.1/S6.6.

New scripts/pack_catalog.py is the single source of truth for the persona/scope
catalog (moved from install_extension_pack.py, re-imported there for
back-compat) plus a per-product artifact resolver: it maps each in-scope
product to the connection references and cloud flows its installed child
solution owns (solution -> solutioncomponents -> intersect object ids). It also
hosts bot_schema (moved here to avoid an import cycle) and a shared recorder,
record_product_steps, that writes productStatus.<product>.<step> for every
in-scope product whose owned artifacts satisfy a per-step predicate.

Each action script main() now records per-product state after its existing
shared _persist_* call, gated on non-dry-run + connector success:
- bind_connections.py    S6.2  predicate: all owned ServiceNow refs bound
                               (shared Dataverse ref stays flat setupStatus.S6.2)
- activate_flows.py      S6.3  predicate: all owned category-5 flows on
- connect_and_share.py   S6.4/S6.5  predicate: owns >=1 cloud flow, gated on
                               the run global connect/share outcome
Recording swallows exceptions so it never changes a script exit code.

Tests: new test_pack_catalog.py and per-product wiring tests in
test_connect_state_wiring.py. 154 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
mukesh4139 and others added 17 commits July 29, 2026 13:45
Delete the 5 ServiceNow-only action scripts (install_extension_pack,
bind_connections, activate_flows, connect_and_share, pack_catalog) and their
tests. Rewrite the skill-6 orchestrator, SKILL.md, and tasks.md so the maker
performs install, bind, flow activation, connect, and share by hand:

- Install (S6.1), flow activation (S6.3), and Dataverse bind (S6.2) stay verified
  by read-only flightcheck (SN-PKG-001, SN-FLOW-*, SN-DV-CONN-001).
- Connect (S6.4) and share (S6.5) are now maker-attested (gate=attest); the
  SN-FLOWCONN-001 programmatic check is no longer gated on since nothing
  establishes its Power Platform token.

Scrub deleted-script references from the retained flightcheck check
(servicenow_flow_binding / SN-FLOWCONN-001), servicenow.py remediation,
registry, pp_env_client, and connect_state docstrings; update the flow-binding
test assertions accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d2f2aaa1-f501-45e5-ad8a-3d7951b8e374
…connection-first ordering

- provision-power-platform-environment (SN): use ServiceNow config paths and
  persist S1.2 on capacity FAILED/WARNING so failures land in
  .local/connect/servicenow/config.json
- permission-gate: add GATE_REASON output so callers can handle a delegated
  admin instead of only halting
- provision-servicenow-entra-user / -certificate: on delegated Entra role,
  hand the admin the full app-registration runbook and resume via the
  Application (client) ID(s)
- install-servicenow-extension-pack + tasks + SKILL: add S6.0 to create the
  ServiceNow and Dataverse connections before pack install; binding/flows stay
  after install

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
Install messages (user + certificate paths) now tell the maker the dialog
auto-picks the ServiceNow and Dataverse connections created in S6.0, and to
only create/change a connection if the right one isn't offered.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
SN-DV-CONN-001 matched every Dataverse connection reference by connector,
so an unrelated unbound reference (the base agent's msdyn_Dataverse) would
fail this ServiceNow S6.2 step even when the pack's own Dataverse reference
was bound and active. Narrow the match to references whose logical name
contains 'sharedcommondataserviceforapps' (the pack family), excluding
msdyn_Dataverse and other system references. Verified PASS against a live
env where msdyn_Dataverse is unbound but the pack reference is bound.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
Turning on flows (S6.3), connecting the flow invoker (S6.4), and sharing the
connection parameters (S6.5) act on artifacts the extension pack installs per
product (each pack ships and enables its own flows; SN-FLOW-* labels every
flow HRSD/ITSM). They were incorrectly mirrored under the shared setupStatus
block. Reclassify S6.3/S6.4/S6.5 as per-product so they mirror under
productStatus.<product> alongside the pack install (S6.1) and portal URL
(S6.6); only the up-front connection creation (S6.0) and the shared bind
(S6.2) stay in setupStatus.

Restructure tasks.md section 6 into dependency order: 6a create (shared) ->
6b install (per-product) -> 6c shared bind -> 6d flows/invoker/share/portal
(per-product). Update config-schema, SKILL resume rule, checklist-updater
example, and the install playbook's checkpoint table and P6.4/P6.5 write
routing. Also clarify the P6.3b Dataverse-bind message to name the specific
unbound reference instead of blaming the pack.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
.local/ runtime state was already ignored, but .vscode/mcp.json was not,
leaving it exposed to accidental commits. Local MCP server config can carry
tenant-specific URLs/keys and must stay local (per PR self-review guidance).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
The check now validates only the ServiceNow pack's own Dataverse reference
(logical name containing 'sharedcommondataserviceforapps') and excludes the
base-agent 'msdyn_Dataverse' and other system references. Update the two
contract tests to encode this ownership boundary, making them stronger:
- all-bound case now includes an UNBOUND msdyn_Dataverse and asserts PASS
  with 'All 1' (base-agent reference ignored, not a failure);
- not-configured case now includes msdyn_Dataverse yet still expects
  NotConfigured when the pack's own reference is absent, and matches the
  updated message marker 'sharedcommondataserviceforapps'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
…roduct

The SN-BASEURL-001 checkpoint previously passed any absolute http(s) URL
present on a product config, so a stale or wrong portal could pass while the
run claimed the confirmed URL was deployed. Compare each in-scope product's
stored ServiceNowPortalBaseURI against the confirmed portalBaseUrl from the
local ServiceNow connect config (host case-insensitive, path exact), report
expected-vs-actual per product, and treat a mismatch as FAILED. Falls back to
presence/format validation when no confirmed value is recorded.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
… completion

The per-product move (S6.1/S6.3/S6.4/S6.5/S6.6) left three prose spots still
routing per-product rows through the flat setupStatus, so resume/render/Done
could disagree with the durable source of truth. Point all three at the single
routing rule in checklist-updater.md: shared rows from setupStatus, per-product
rows from productStatus.<product>.

- install-servicenow-extension-pack.md Done: S6.3/S6.4/S6.5 are per-product.
- SKILL.md checklist render: use the same routing as the resume-point rule.
- SKILL.md persist note: durable mirror is setupStatus or productStatus by row.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
_categorize_servicenow_flows checked the 'HR Service' substring first, so a
real ITSM flow named e.g. 'ESS HR ServiceNow ITSM Common Orchestrator' matched
HRSD via its 'HR ServiceNow' host-agent prefix. The live run then reported all
seven flows as HRSD, corrupting per-product completion evidence.

Match the explicit product token (ITSM / HRSD) case-insensitively, checking
ITSM first, and never treat the host-agent prefix as a product signal. Add
tests asserting each SN-FLOW row's [HRSD]/[ITSM] label, a host-prefix
regression case, and direct coverage of the categorizer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
The install playbook required SN-PKG-001 to gate S6.1, but nothing emitted it,
so the agent improvised install evidence from unrelated flow/config rows and
S6.1 was neither deterministic nor auditable.

Add _check_pack_install: per-product install verification keyed on each pack's
Dataverse template-config scenario records (EXPECTED_TEMPLATE_CONFIGS, the same
artifact SN-CFG reads) — all present -> Passed, some -> Failed (partial /
reinstall), none -> NotConfigured. Emits a summary SN-PKG-001 (naming each
product's state) plus per-product SN-PKG-010 (HRSD) / SN-PKG-020 (ITSM). Wire
into the deep run and register run_servicenow_pack_checks as a self-contained
--checkpoint SN-PKG-001 so it runs before any flow exists.

Update the playbook to verify via --checkpoint SN-PKG-001 and document the
per-product evidence contract. Add tests for HR-only, IT-only, both, partial,
not-installed, reinstall recovery, token-skip, query-error, and registration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
…rifiable

P6.6 treated the Portal Base URL step as an attest gate and accepted a maker
attestation (ACK=true -> done) even when SN-BASEURL-001 returned Failed, letting
a failed programmatic gate complete setup and hiding broken case/ticket links.

Rewrite the P6.6 decision as a status-driven state machine consistent with the
shared U.2 prog rule: PASSED -> done; FAILED (Dataverse read an empty/malformed/
mismatched value) -> never attest, keep in-progress, show remediation, re-run
--checkpoint SN-BASEURL-001 and loop until it passes; NotConfigured -> back to
P6.2; attestation allowed ONLY when verification is genuinely unavailable
(Skipped/Warning). Update the checkpoint-table and tasks.md S6.6 gate notes to
'prog; attest only if unverifiable, never on Failed'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
…tructive re-runs

Adds a supported post-completion 'Add a product' transition to the ServiceNow
setup router (HR->Both / IT->Both): preserves completed state, initializes
only the newly scoped product, inserts only its group-6 checklist rows,
invalidates only cross-product validation (S7.1), and resumes at the new
product's install step. Hardens the skill-3 capture P3.3 merge so a re-run
applies null/empty first-run defaults only when a key is absent, never
clobbering portalBaseUrl, scope flags, or completed status blocks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
Finding #7: G.1's role-not-held path asked 'Do you have this role?' and
let 'Yes' flip a proven-absent programmatic result to an attested pass. It
also treated Authorization_RequestDenied (query unavailable) as proven
absence. Split the two: a successful query returning roles without the
required one is proven absence and can only stop (acquire/delegate/re-run,
never attest); a query that could not read the directory falls back to the
G.2 attestation gate, where attestation-to-pass is legitimate. Reworded the
stale G.2 cross-reference to be self-contained.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
S6.0 previously completed on maker attestation alone, so a missing or
unhealthy ServiceNow/Dataverse connection was only caught later at the S6.2
bind or S6.4 flow invoker. Add a programmatic checkpoint that reads the Power
Platform connection inventory and asserts both the ServiceNow and Dataverse
connection objects exist and are Connected:

- checks/servicenow.py: _check_connection_objects + self-contained
  run_servicenow_connection_object_checks wrapper; run in the deep pass.
  PASSED completes S6.0; NotConfigured (missing) / Failed (unhealthy) block;
  MANUAL (inventory unreadable) falls back to maker attestation.
- registry.py: register SN-CONN-OBJECTS-001 (PP_ADMIN client, HIGH).
- Skill docs (SKILL.md, install-servicenow-extension-pack.md, tasks.md):
  S6.0 is now prog (maker creates, checkpoint verifies), attest only on
  MANUAL; P6.0 runs the checkpoint and loops until it passes.
- Correct the HRSD template config name ServiceNowHRSDGetCasesList ->
  ServiceNowHRSDGetUserCases in EXPECTED_TEMPLATE_CONFIGS and the pack/
  template-config test fixtures.
- Add 4 SN-CONN-OBJECTS-001 tests (both-connected, missing, unhealthy,
  unreadable-inventory).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
Two UX fixes in the extension-pack playbook so the maker isn't asked to do
manual work that may be unnecessary:

- P6.4 (turn on flows): verify the SN-FLOW-* rows FIRST and only guide the
  maker to Power Platform when a flow is actually off. When every in-scope
  flow already passes, complete S6.3 without prompting.
- P6.5 (connect + share): bundle the connect-invoker and OBO-share steps into
  a single instruction block and one 3-option question (both complete /
  connected-but-not-shared / not-connected). Derive S6.4 and S6.5 from that
  single bundled answer instead of two separate prompts, while still recording
  the two attested stages separately for accurate resume.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
Follow-on to the bundled P6.5 connect-and-share step: the end-to-end
validation now reads parameterSharing from saved ServiceNow setup state
before prompting. When it is already "shared" (or sharing is not applicable
for the chosen connector/auth mode), carry that prior attestation forward as
S7.1 evidence and skip the sharing question; only ask when the state is
unset. A "Yes" answer now persists parameterSharing = "shared" so later
resumes don't re-ask.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c98614a8-d171-40ba-9773-2928d36cffe2
@mukesh4139
mukesh4139 marked this pull request as ready for review July 30, 2026 17:13
@mukesh4139
mukesh4139 marked this pull request as draft July 31, 2026 16:58
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.

1 participant