@bpmsoftwaresolutions/ai-engine-client 1.1.64 → 1.1.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/package.json +1 -1
- package/src/index.js +306 -0
package/README.md
CHANGED
|
@@ -94,6 +94,34 @@ const continuation = await client.resumeProjectWork({
|
|
|
94
94
|
|
|
95
95
|
That call resolves the canonical project, active roadmap state, workflow context, tool bindings, and the continuation brief in one governed response. Use the returned `continuation_brief_markdown` as the primary focus surface for LOGA and operator startup flows.
|
|
96
96
|
|
|
97
|
+
### Roadmap Closure Workflow
|
|
98
|
+
|
|
99
|
+
Use `closeRoadmapItemWorkflow(...)` when you want the client to compose the full roadmap-closure path end to end.
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const closure = await client.closeRoadmapItemWorkflow({
|
|
103
|
+
projectIdentifier: charter.project_id,
|
|
104
|
+
claimId: existingClaimId,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The helper:
|
|
109
|
+
|
|
110
|
+
- resumes the project
|
|
111
|
+
- loads the active roadmap item
|
|
112
|
+
- validates a supplied claim with `claimIsValid(claimId)`
|
|
113
|
+
- starts a fresh claim when the supplied claim is missing or inactive
|
|
114
|
+
- loads and verifies acceptance checks
|
|
115
|
+
- verifies the required artifacts
|
|
116
|
+
- records the implementation gate decision
|
|
117
|
+
- advances the item to terminal status
|
|
118
|
+
- attaches closure evidence
|
|
119
|
+
- signs off the claim
|
|
120
|
+
- reloads the active item
|
|
121
|
+
- closes the active project when no open tasks remain
|
|
122
|
+
|
|
123
|
+
The backend still does not expose a project-scoped `getActiveClaim(projectId)` helper. This client records that gap and uses `claimIsValid(claimId)` plus claim creation when it needs a governed fallback path.
|
|
124
|
+
|
|
97
125
|
## Integration Notes
|
|
98
126
|
|
|
99
127
|
### Auth Modes
|
|
@@ -143,6 +171,8 @@ console.log(claimed.claim_id, claimed.context_session_id);
|
|
|
143
171
|
|
|
144
172
|
This complements the lower-level flow exposed by `openContextSession()`, `acknowledgeReminder()`, `completeOrientation()`, `lockContextSessionClaim()`, `claimWorkItem()`, and `signoffClaim()`.
|
|
145
173
|
|
|
174
|
+
For closure and write preflights, call `claimIsValid(claimId)` before you attempt roadmap mutations. The client does not yet expose a project-scoped `getActiveClaim(projectId)` helper because the backend currently only offers claim reads by claim id or by context session.
|
|
175
|
+
|
|
146
176
|
### Text and Binary Downloads
|
|
147
177
|
|
|
148
178
|
Some methods do not return plain JSON:
|
|
@@ -422,8 +452,8 @@ const packet = await client.downloadExternalWorkflowRunArtifact('run-123', 'pack
|
|
|
422
452
|
| `ping()` | Health check + current workflow name/status. |
|
|
423
453
|
| `startSessionGovernance(body)` | Start a governed external session and return the `session_key` required for mutation-capable assistant turns. |
|
|
424
454
|
| `claimWorkItem(body)` | Bind an oriented, claim-locked context session to a concrete work item and return the active claim envelope. |
|
|
455
|
+
| `claimIsValid(claimId)` | Read a claim by id and confirm that it is still active before attempting a governed write. |
|
|
425
456
|
| `persistAssistantTurn(body)` | Persist an assistant turn to durable SQL through the API and return refreshed status projections. |
|
|
426
|
-
| `resumeProjectWork({ projectIdentifier, actorMode, executionIntent, requireClaim, workflowRunLimit })` | Canonical project startup and hydration call that resolves the continuation brief, active item, required tools, allowed scope, and next governed action from a project identifier. |
|
|
427
457
|
| `createExternalAudioRender({ text, voice, model, speed, file })` | Create a client-scoped external audio render using inline text or multipart upload. |
|
|
428
458
|
| `getExternalAudioRender(audioRenderRunId)` | Read external audio render status for the authenticated client. |
|
|
429
459
|
| `downloadExternalAudioRender(audioRenderRunId)` | Download the rendered MP3 artifact for the authenticated client. |
|
|
@@ -593,6 +623,7 @@ Generated scripts are ephemeral helpers, not source-of-truth. The server keeps S
|
|
|
593
623
|
| `downloadProjectMarkdownReport(projectId, reportType)` | Download a rendered markdown report as text plus filename metadata. |
|
|
594
624
|
| `downloadProjectCharterReportMarkdown(projectId)` | Download the charter markdown report. |
|
|
595
625
|
| `getProjectBundle(projectId)` | Get current status, charter report, and roadmap report in one payload. |
|
|
626
|
+
| `resumeProjectWork({ projectIdentifier, actorMode, executionIntent, requireClaim, workflowRunLimit })` | Resume the governed continuation brief for a project. |
|
|
596
627
|
|
|
597
628
|
### Roadmaps
|
|
598
629
|
| Method | Description |
|
|
@@ -601,6 +632,7 @@ Generated scripts are ephemeral helpers, not source-of-truth. The server keeps S
|
|
|
601
632
|
| `getProjectRoadmap(projectId)` | Get full roadmap for a project. |
|
|
602
633
|
| `getProjectRoadmapSummary(projectId)` | Roadmap summary. |
|
|
603
634
|
| `getProjectRoadmapActiveItem(projectId)` | Current active roadmap item. |
|
|
635
|
+
| `closeRoadmapItemWorkflow({ projectIdentifier, claimId, actorMode, executionIntent, workflowRunLimit, requiredArtifacts, requiredAcceptanceCheckStatus, terminalItemStatus, gateType, gateDecision, closeProjectIfNoRemainingOpenItems })` | Compose roadmap closure end to end: resume, claim fallback, verify checks/artifacts, record a gate decision, sign off, and close the project when nothing remains open. |
|
|
604
636
|
| `getProjectImplementationRoadmapReport(projectId)` | Get the SQL-backed implementation roadmap report payload. |
|
|
605
637
|
| `downloadProjectImplementationRoadmapReportMarkdown(projectId)` | Download the implementation roadmap markdown report. |
|
|
606
638
|
| `ensureProjectRoadmapTaskSurface(projectId, { requestedBy, assignedTo, createAcceptanceSubtasks })` | Materialize the active roadmap item's parent task and acceptance subtasks. |
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -3558,6 +3558,11 @@ export class AIEngineClient {
|
|
|
3558
3558
|
return this._request(`/api/governance/claims/${encodeURIComponent(claimId)}`);
|
|
3559
3559
|
}
|
|
3560
3560
|
|
|
3561
|
+
async claimIsValid(claimId) {
|
|
3562
|
+
const claim = await this.getClaim(claimId);
|
|
3563
|
+
return cleanText(claim?.status) === 'active';
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3561
3566
|
async getExecutionEligibility(body = {}) {
|
|
3562
3567
|
return this._request('/api/governance/execution-eligibility', {
|
|
3563
3568
|
method: 'POST',
|
|
@@ -4368,6 +4373,307 @@ export class AIEngineClient {
|
|
|
4368
4373
|
return this._request(`/api/operator/projects/${projectId}/bundle`);
|
|
4369
4374
|
}
|
|
4370
4375
|
|
|
4376
|
+
async resumeProjectWork({
|
|
4377
|
+
projectIdentifier,
|
|
4378
|
+
projectId,
|
|
4379
|
+
actorMode,
|
|
4380
|
+
executionIntent,
|
|
4381
|
+
requireClaim,
|
|
4382
|
+
workflowRunLimit,
|
|
4383
|
+
} = {}) {
|
|
4384
|
+
const normalizedProjectIdentifier = cleanText(projectIdentifier) || cleanText(projectId);
|
|
4385
|
+
if (!normalizedProjectIdentifier) {
|
|
4386
|
+
throw new Error('projectIdentifier is required.');
|
|
4387
|
+
}
|
|
4388
|
+
return this._request(`/api/operator/projects/${encodeURIComponent(normalizedProjectIdentifier)}/resume-context`, {
|
|
4389
|
+
query: {
|
|
4390
|
+
actor_mode: actorMode,
|
|
4391
|
+
execution_intent: executionIntent,
|
|
4392
|
+
require_claim: requireClaim,
|
|
4393
|
+
workflow_run_limit: workflowRunLimit,
|
|
4394
|
+
},
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
async closeRoadmapItemWorkflow({
|
|
4399
|
+
projectIdentifier,
|
|
4400
|
+
projectId,
|
|
4401
|
+
claimId,
|
|
4402
|
+
claimName,
|
|
4403
|
+
actorId,
|
|
4404
|
+
actorMode = 'operator',
|
|
4405
|
+
executionIntent = 'close roadmap item workflow',
|
|
4406
|
+
workflowRunLimit = 5,
|
|
4407
|
+
declaredScopeFiles,
|
|
4408
|
+
allowedMutationSurfaces,
|
|
4409
|
+
requiredArtifacts,
|
|
4410
|
+
requiredAcceptanceCheckStatus = 'verified',
|
|
4411
|
+
terminalItemStatus = 'accepted',
|
|
4412
|
+
gateType = 'roadmap_closure',
|
|
4413
|
+
gateDecision = 'pass',
|
|
4414
|
+
gateRationale,
|
|
4415
|
+
gateEvidenceRefs,
|
|
4416
|
+
remediationActions = [],
|
|
4417
|
+
closureEvidenceType = 'roadmap_closure',
|
|
4418
|
+
closureEvidenceRef,
|
|
4419
|
+
closureEvidenceTitle,
|
|
4420
|
+
closeProjectIfNoRemainingOpenItems = true,
|
|
4421
|
+
closeProjectReason,
|
|
4422
|
+
} = {}) {
|
|
4423
|
+
const normalizedProjectReference = cleanText(projectIdentifier) || cleanText(projectId);
|
|
4424
|
+
if (!normalizedProjectReference) {
|
|
4425
|
+
throw new Error('projectIdentifier is required.');
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
const normalizedActorId = cleanText(actorId) || this.actorId;
|
|
4429
|
+
const continuation = await this.resumeProjectWork({
|
|
4430
|
+
projectIdentifier: normalizedProjectReference,
|
|
4431
|
+
projectId: normalizedProjectReference,
|
|
4432
|
+
actorMode,
|
|
4433
|
+
executionIntent,
|
|
4434
|
+
requireClaim: false,
|
|
4435
|
+
workflowRunLimit,
|
|
4436
|
+
});
|
|
4437
|
+
|
|
4438
|
+
const projectSummary = isPlainObject(continuation?.project) ? continuation.project : {};
|
|
4439
|
+
const resolvedProjectId = cleanText(projectSummary.project_id) || cleanText(projectSummary.projectId) || normalizedProjectReference;
|
|
4440
|
+
const resolvedWorkflowId = cleanText(projectSummary.workflow_id) || cleanText(projectSummary.workflowId);
|
|
4441
|
+
const resolvedWorkflowRunId = cleanText(projectSummary.workflow_run_id) || cleanText(projectSummary.workflowRunId) || null;
|
|
4442
|
+
const activeItem = await this.getProjectRoadmapActiveItem(resolvedProjectId);
|
|
4443
|
+
const normalizedActiveItem = isPlainObject(activeItem) ? activeItem : {};
|
|
4444
|
+
const implementationItemId = cleanText(normalizedActiveItem.implementation_item_id) || cleanText(normalizedActiveItem.implementationItemId);
|
|
4445
|
+
const itemKey = cleanText(normalizedActiveItem.item_key) || cleanText(normalizedActiveItem.itemKey) || implementationItemId;
|
|
4446
|
+
const itemStatus = cleanText(normalizedActiveItem.item_status) || cleanText(normalizedActiveItem.status);
|
|
4447
|
+
const packetId = cleanText(normalizedActiveItem.implementation_packet_id) || cleanText(normalizedActiveItem.implementationPacketId);
|
|
4448
|
+
|
|
4449
|
+
if (!implementationItemId || !resolvedWorkflowId || !packetId) {
|
|
4450
|
+
throw new Error('project closure state is ambiguous.');
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
let effectiveClaimId = cleanText(claimId);
|
|
4454
|
+
let claimSource = 'provided';
|
|
4455
|
+
if (effectiveClaimId) {
|
|
4456
|
+
const claimStillValid = await this.claimIsValid(effectiveClaimId).catch(() => false);
|
|
4457
|
+
if (!claimStillValid) {
|
|
4458
|
+
effectiveClaimId = null;
|
|
4459
|
+
claimSource = 'fresh';
|
|
4460
|
+
}
|
|
4461
|
+
} else {
|
|
4462
|
+
claimSource = 'fresh';
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
let freshClaim = null;
|
|
4466
|
+
if (!effectiveClaimId) {
|
|
4467
|
+
const declaredScope = cleanList(declaredScopeFiles);
|
|
4468
|
+
const surfaces = cleanList(allowedMutationSurfaces);
|
|
4469
|
+
const fallbackDeclaredScope = declaredScope.length > 0 ? declaredScope : [
|
|
4470
|
+
`${resolvedProjectId}`,
|
|
4471
|
+
itemKey || implementationItemId,
|
|
4472
|
+
].filter(Boolean);
|
|
4473
|
+
const fallbackSurfaces = surfaces.length > 0 ? surfaces : [
|
|
4474
|
+
'project_roadmap_task',
|
|
4475
|
+
'implementation_acceptance_check',
|
|
4476
|
+
'implementation_evidence',
|
|
4477
|
+
'implementation_gate',
|
|
4478
|
+
'project_close',
|
|
4479
|
+
];
|
|
4480
|
+
const closureTaskStatus = itemStatus && !new Set(['done', 'completed', 'complete', 'blocked', 'failed', 'rejected', 'cancelled']).has(itemStatus.toLowerCase())
|
|
4481
|
+
? itemStatus
|
|
4482
|
+
: 'in_progress';
|
|
4483
|
+
const closureTaskBinding = {
|
|
4484
|
+
task_id: implementationItemId || itemKey || resolvedProjectId,
|
|
4485
|
+
roadmap_item_id: implementationItemId || itemKey || resolvedProjectId,
|
|
4486
|
+
task_status: closureTaskStatus,
|
|
4487
|
+
allowed_actions: ['claim_work_item'],
|
|
4488
|
+
substrate_action: 'claim_work_item',
|
|
4489
|
+
task_type: 'roadmap_closure',
|
|
4490
|
+
};
|
|
4491
|
+
try {
|
|
4492
|
+
freshClaim = await this.startClaimedWork({
|
|
4493
|
+
claimName: cleanText(claimName) || `closeRoadmapItemWorkflow:${resolvedProjectId}:${itemKey || implementationItemId}`,
|
|
4494
|
+
actorId: normalizedActorId,
|
|
4495
|
+
intentId: 'code_mutation',
|
|
4496
|
+
declaredScopeFiles: fallbackDeclaredScope,
|
|
4497
|
+
allowedMutationSurfaces: fallbackSurfaces,
|
|
4498
|
+
runtimeSessionId: resolvedWorkflowRunId || resolvedProjectId,
|
|
4499
|
+
executionPurpose: cleanText(executionIntent) || `Close roadmap item ${itemKey || implementationItemId}`,
|
|
4500
|
+
successCriteria: 'Active roadmap item closed with verified acceptance checks, artifacts, gate decision, and claim signoff.',
|
|
4501
|
+
mutationAllowed: true,
|
|
4502
|
+
metadata: {
|
|
4503
|
+
source: 'closeRoadmapItemWorkflow',
|
|
4504
|
+
project_id: resolvedProjectId,
|
|
4505
|
+
workflow_id: resolvedWorkflowId,
|
|
4506
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4507
|
+
task_binding: closureTaskBinding,
|
|
4508
|
+
},
|
|
4509
|
+
});
|
|
4510
|
+
} catch (error) {
|
|
4511
|
+
throw new Error(`claim cannot be established: ${error.message}`);
|
|
4512
|
+
}
|
|
4513
|
+
effectiveClaimId = cleanText(freshClaim?.claim_id) || cleanText(freshClaim?.claim?.claim_id);
|
|
4514
|
+
if (!effectiveClaimId) {
|
|
4515
|
+
throw new Error('claim cannot be established.');
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
const acceptanceCheckResult = await this.getImplementationItemAcceptanceChecks(implementationItemId);
|
|
4520
|
+
const acceptanceChecks = Array.isArray(acceptanceCheckResult?.acceptance_checks)
|
|
4521
|
+
? acceptanceCheckResult.acceptance_checks.filter((check) => isPlainObject(check))
|
|
4522
|
+
: [];
|
|
4523
|
+
if (acceptanceChecks.length === 0) {
|
|
4524
|
+
throw new Error('acceptance checks are incomplete.');
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
const normalizedAcceptanceStatus = cleanText(requiredAcceptanceCheckStatus) || 'verified';
|
|
4528
|
+
const verifiedAcceptanceChecks = [];
|
|
4529
|
+
for (const check of acceptanceChecks) {
|
|
4530
|
+
const acceptanceCheckId = cleanText(check.acceptance_check_id) || cleanText(check.acceptanceCheckId);
|
|
4531
|
+
if (!acceptanceCheckId) {
|
|
4532
|
+
throw new Error('acceptance checks are incomplete.');
|
|
4533
|
+
}
|
|
4534
|
+
const status = cleanText(check.status).toLowerCase();
|
|
4535
|
+
if (status !== normalizedAcceptanceStatus.toLowerCase()) {
|
|
4536
|
+
try {
|
|
4537
|
+
const verification = await this.updateAcceptanceCheckStatusVerified(
|
|
4538
|
+
implementationItemId,
|
|
4539
|
+
acceptanceCheckId,
|
|
4540
|
+
normalizedAcceptanceStatus,
|
|
4541
|
+
{
|
|
4542
|
+
updated_by: normalizedActorId,
|
|
4543
|
+
workflowId: resolvedWorkflowId,
|
|
4544
|
+
claim_id: effectiveClaimId,
|
|
4545
|
+
status_reason: 'Roadmap closure workflow verified the required acceptance check.',
|
|
4546
|
+
},
|
|
4547
|
+
);
|
|
4548
|
+
verifiedAcceptanceChecks.push({
|
|
4549
|
+
acceptance_check_id: acceptanceCheckId,
|
|
4550
|
+
status: normalizedAcceptanceStatus,
|
|
4551
|
+
verification,
|
|
4552
|
+
});
|
|
4553
|
+
} catch (error) {
|
|
4554
|
+
throw new Error(`acceptance checks are incomplete: ${error.message}`);
|
|
4555
|
+
}
|
|
4556
|
+
continue;
|
|
4557
|
+
}
|
|
4558
|
+
verifiedAcceptanceChecks.push({
|
|
4559
|
+
acceptance_check_id: acceptanceCheckId,
|
|
4560
|
+
status,
|
|
4561
|
+
existing: true,
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
const artifacts = await this.verifyImplementationItemArtifacts(implementationItemId, {
|
|
4566
|
+
requiredArtifacts: cleanList(requiredArtifacts).length > 0
|
|
4567
|
+
? cleanList(requiredArtifacts)
|
|
4568
|
+
: ['artifact_manifest', 'decision_packet'],
|
|
4569
|
+
});
|
|
4570
|
+
|
|
4571
|
+
const evidenceRefs = cleanList(gateEvidenceRefs);
|
|
4572
|
+
if (evidenceRefs.length === 0) {
|
|
4573
|
+
evidenceRefs.push(closureEvidenceRef || `roadmap-closure:${resolvedProjectId}:${itemKey || implementationItemId}`);
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
const gateDecisionResult = await this.createImplementationPacketGateDecision(packetId, {
|
|
4577
|
+
gate_type: cleanText(gateType) || 'roadmap_closure',
|
|
4578
|
+
decision: cleanText(gateDecision) || 'pass',
|
|
4579
|
+
rationale: cleanText(gateRationale) || `Roadmap item ${itemKey || implementationItemId} satisfied all closure checks.`,
|
|
4580
|
+
evidence_refs: evidenceRefs,
|
|
4581
|
+
remediation_actions: cleanList(remediationActions),
|
|
4582
|
+
decided_by: normalizedActorId,
|
|
4583
|
+
claim_id: effectiveClaimId,
|
|
4584
|
+
workflow_id: resolvedWorkflowId,
|
|
4585
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4586
|
+
});
|
|
4587
|
+
|
|
4588
|
+
const itemStatusResult = await this.updateImplementationItemStatusVerified(
|
|
4589
|
+
implementationItemId,
|
|
4590
|
+
cleanText(terminalItemStatus) || 'accepted',
|
|
4591
|
+
{
|
|
4592
|
+
workflowId: resolvedWorkflowId,
|
|
4593
|
+
updated_by: normalizedActorId,
|
|
4594
|
+
claim_id: effectiveClaimId,
|
|
4595
|
+
status_reason: cleanText(closeProjectReason) || `Roadmap closure workflow advanced ${itemKey || implementationItemId} to terminal status.`,
|
|
4596
|
+
},
|
|
4597
|
+
);
|
|
4598
|
+
|
|
4599
|
+
const closureEvidenceRefValue = cleanText(closureEvidenceRef) || `roadmap-closure:${resolvedProjectId}:${itemKey || implementationItemId}`;
|
|
4600
|
+
const closureEvidence = await this.addImplementationItemEvidence(implementationItemId, {
|
|
4601
|
+
evidence_type: cleanText(closureEvidenceType) || 'roadmap_closure',
|
|
4602
|
+
evidence_ref: closureEvidenceRefValue,
|
|
4603
|
+
title: cleanText(closureEvidenceTitle) || `Closure evidence for ${itemKey || implementationItemId}`,
|
|
4604
|
+
recorded_by: normalizedActorId,
|
|
4605
|
+
metadata: {
|
|
4606
|
+
project_id: resolvedProjectId,
|
|
4607
|
+
workflow_id: resolvedWorkflowId,
|
|
4608
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4609
|
+
claim_id: effectiveClaimId,
|
|
4610
|
+
gate_decision_id: cleanText(gateDecisionResult?.implementation_gate_decision_id) || cleanText(gateDecisionResult?.gate_decision_id),
|
|
4611
|
+
gate_type: cleanText(gateType) || 'roadmap_closure',
|
|
4612
|
+
},
|
|
4613
|
+
});
|
|
4614
|
+
|
|
4615
|
+
const signedClaim = await this.signoffClaim(effectiveClaimId, {
|
|
4616
|
+
actor_signature: normalizedActorId,
|
|
4617
|
+
intent_confirmation: cleanText(executionIntent) || 'I confirm this governed roadmap closure workflow.',
|
|
4618
|
+
});
|
|
4619
|
+
|
|
4620
|
+
const reloadedActiveItem = await this.getProjectRoadmapActiveItem(resolvedProjectId);
|
|
4621
|
+
const remainingOpenTasksPayload = await this.listProjectOpenTasks(resolvedProjectId);
|
|
4622
|
+
const remainingOpenTasks = Array.isArray(remainingOpenTasksPayload?.tasks)
|
|
4623
|
+
? remainingOpenTasksPayload.tasks.filter((task) => isPlainObject(task))
|
|
4624
|
+
: Array.isArray(remainingOpenTasksPayload)
|
|
4625
|
+
? remainingOpenTasksPayload.filter((task) => isPlainObject(task))
|
|
4626
|
+
: [];
|
|
4627
|
+
const reloadedItemStatus = cleanText(reloadedActiveItem?.item_status) || cleanText(reloadedActiveItem?.status) || null;
|
|
4628
|
+
const terminalStatuses = new Set([
|
|
4629
|
+
cleanText(terminalItemStatus) || 'accepted',
|
|
4630
|
+
'accepted',
|
|
4631
|
+
'verified',
|
|
4632
|
+
'done',
|
|
4633
|
+
'completed',
|
|
4634
|
+
'closed',
|
|
4635
|
+
].map((value) => String(value || '').toLowerCase()));
|
|
4636
|
+
const activeItemClosed = !reloadedActiveItem || terminalStatuses.has(String(reloadedItemStatus || '').toLowerCase());
|
|
4637
|
+
if (!activeItemClosed) {
|
|
4638
|
+
throw new Error('project closure state is ambiguous.');
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
let closeProjectResult = null;
|
|
4642
|
+
if (closeProjectIfNoRemainingOpenItems && remainingOpenTasks.length === 0) {
|
|
4643
|
+
closeProjectResult = await this.closeActiveProject(resolvedProjectId, {
|
|
4644
|
+
workflowId: resolvedWorkflowId,
|
|
4645
|
+
workflowRunIds: resolvedWorkflowRunId ? [resolvedWorkflowRunId] : undefined,
|
|
4646
|
+
reason: cleanText(closeProjectReason) || `Roadmap item ${itemKey || implementationItemId} closed.`,
|
|
4647
|
+
operatorIdentity: normalizedActorId,
|
|
4648
|
+
runTerminalStatus: cleanText(terminalItemStatus) || 'accepted',
|
|
4649
|
+
});
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
return {
|
|
4653
|
+
status: closeProjectResult ? 'closed' : 'item_closed',
|
|
4654
|
+
project_id: resolvedProjectId,
|
|
4655
|
+
workflow_id: resolvedWorkflowId,
|
|
4656
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4657
|
+
claim_id: effectiveClaimId,
|
|
4658
|
+
claim_source: claimSource,
|
|
4659
|
+
continuation_brief: continuation,
|
|
4660
|
+
project: projectSummary,
|
|
4661
|
+
active_item_before: normalizedActiveItem,
|
|
4662
|
+
active_item_after: reloadedActiveItem,
|
|
4663
|
+
acceptance_checks: {
|
|
4664
|
+
loaded: acceptanceChecks,
|
|
4665
|
+
verified: verifiedAcceptanceChecks,
|
|
4666
|
+
},
|
|
4667
|
+
artifact_verification: artifacts,
|
|
4668
|
+
gate_decision: gateDecisionResult,
|
|
4669
|
+
implementation_item_status: itemStatusResult,
|
|
4670
|
+
closure_evidence: closureEvidence,
|
|
4671
|
+
claim_signoff: signedClaim,
|
|
4672
|
+
remaining_open_task_count: remainingOpenTasks.length,
|
|
4673
|
+
close_project: closeProjectResult,
|
|
4674
|
+
};
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4371
4677
|
// LOGA projections are governed runtime documents, not static reports.
|
|
4372
4678
|
|
|
4373
4679
|
async getLogaOperatorHomeProjection() {
|