@absolutejs/voice 0.0.22-beta.375 → 0.0.22-beta.377
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 +10 -0
- package/dist/client/index.js +126 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +127 -0
- package/dist/proofTrends.d.ts +16 -0
- package/dist/react/index.js +126 -0
- package/dist/vue/index.js +126 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1545,6 +1545,16 @@ const recoveryJobs = createVoiceSQLiteRealCallProfileRecoveryJobStore({
|
|
|
1545
1545
|
});
|
|
1546
1546
|
```
|
|
1547
1547
|
|
|
1548
|
+
Recovery routes expose recent persisted jobs at `${path}/actions/jobs`. Stores can implement `list({ limit, actionId, status })`; the bundled memory and SQLite stores both support it.
|
|
1549
|
+
|
|
1550
|
+
Use `buildVoiceRealCallProfileRecoveryJobHistoryCheck(...)` in production readiness to prove recovery jobs are persisted and inspectable:
|
|
1551
|
+
|
|
1552
|
+
```ts
|
|
1553
|
+
additionalChecks: async () => [
|
|
1554
|
+
await buildVoiceRealCallProfileRecoveryJobHistoryCheck(recoveryJobs)
|
|
1555
|
+
];
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1548
1558
|
Use `createVoiceProfileTraceTagger(...)` when the app already has a trace store and needs every appended trace to carry a benchmark profile label. It wraps any `VoiceTraceEventStore`, preserves the underlying store behavior, and adds `profileId`/`benchmarkProfileId` metadata and payload fields that real-call profile history can ingest later.
|
|
1549
1559
|
|
|
1550
1560
|
```ts
|
package/dist/client/index.js
CHANGED
|
@@ -4649,6 +4649,13 @@ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
4649
4649
|
get(id) {
|
|
4650
4650
|
return jobs.get(id);
|
|
4651
4651
|
},
|
|
4652
|
+
list(input = {}) {
|
|
4653
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
4654
|
+
return [...jobs.values()].filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).sort((first, second) => {
|
|
4655
|
+
const updatedDelta = Date.parse(second.updatedAt) - Date.parse(first.updatedAt);
|
|
4656
|
+
return updatedDelta === 0 ? second.id.localeCompare(first.id) : updatedDelta;
|
|
4657
|
+
}).slice(0, limit);
|
|
4658
|
+
},
|
|
4652
4659
|
update(id, update) {
|
|
4653
4660
|
const existing = jobs.get(id);
|
|
4654
4661
|
if (!existing) {
|
|
@@ -4682,6 +4689,7 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
4682
4689
|
payload TEXT NOT NULL
|
|
4683
4690
|
)`);
|
|
4684
4691
|
const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
|
|
4692
|
+
const listStatement = database.query(`SELECT payload FROM "${tableName}" ORDER BY sort_at DESC, id DESC`);
|
|
4685
4693
|
const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
|
|
4686
4694
|
VALUES (?1, ?2, ?3)
|
|
4687
4695
|
ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
|
|
@@ -4708,6 +4716,10 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
4708
4716
|
get(id) {
|
|
4709
4717
|
return readJob(id);
|
|
4710
4718
|
},
|
|
4719
|
+
list(input = {}) {
|
|
4720
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
4721
|
+
return listStatement.all().map((row) => JSON.parse(row.payload)).filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).slice(0, limit);
|
|
4722
|
+
},
|
|
4711
4723
|
update(id, update) {
|
|
4712
4724
|
const existing = readJob(id);
|
|
4713
4725
|
if (!existing) {
|
|
@@ -4748,6 +4760,94 @@ var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
|
|
|
4748
4760
|
value: `${String(report.defaults.summary.actionableProfiles)}/${String(report.summary.profileCount)} actionable`
|
|
4749
4761
|
};
|
|
4750
4762
|
};
|
|
4763
|
+
var buildVoiceRealCallProfileRecoveryJobHistoryCheck = async (store, options = {}) => {
|
|
4764
|
+
const href = options.href ?? "/voice/real-call-profile-recovery";
|
|
4765
|
+
const sourceHref = options.sourceHref ?? "/api/voice/real-call-profile-history/actions/jobs";
|
|
4766
|
+
const minCompletedJobs = options.minCompletedJobs ?? 1;
|
|
4767
|
+
const label = options.label ?? "Real-call recovery job history";
|
|
4768
|
+
if (!store?.list) {
|
|
4769
|
+
return {
|
|
4770
|
+
actions: [
|
|
4771
|
+
{
|
|
4772
|
+
description: "Configure a recovery job store with list support so operators can inspect proof repair history.",
|
|
4773
|
+
href,
|
|
4774
|
+
label: "Configure recovery job history"
|
|
4775
|
+
}
|
|
4776
|
+
],
|
|
4777
|
+
detail: "No real-call profile recovery job store with list support is configured.",
|
|
4778
|
+
gateExplanation: {
|
|
4779
|
+
evidenceHref: sourceHref,
|
|
4780
|
+
observed: "missing",
|
|
4781
|
+
remediation: "Use the bundled memory or SQLite recovery job store, or implement list() on the custom store.",
|
|
4782
|
+
sourceHref,
|
|
4783
|
+
threshold: "list support",
|
|
4784
|
+
thresholdLabel: "Inspectable recovery jobs",
|
|
4785
|
+
unit: "status"
|
|
4786
|
+
},
|
|
4787
|
+
href,
|
|
4788
|
+
label,
|
|
4789
|
+
status: "fail",
|
|
4790
|
+
value: "missing"
|
|
4791
|
+
};
|
|
4792
|
+
}
|
|
4793
|
+
const jobs = await store.list({ limit: 50 });
|
|
4794
|
+
const now = Date.now();
|
|
4795
|
+
const recentJobs = options.maxAgeMs === undefined ? jobs : jobs.filter((job) => now - Date.parse(job.updatedAt) <= options.maxAgeMs);
|
|
4796
|
+
const completedJobs = recentJobs.filter((job) => job.status === "pass" || job.status === "fail");
|
|
4797
|
+
const failedJobs = recentJobs.filter((job) => job.status === "fail");
|
|
4798
|
+
const runningJobs = recentJobs.filter((job) => job.status === "queued" || job.status === "running");
|
|
4799
|
+
const issues = [];
|
|
4800
|
+
const warnings = [];
|
|
4801
|
+
if (completedJobs.length < minCompletedJobs) {
|
|
4802
|
+
issues.push(`Expected at least ${String(minCompletedJobs)} completed recovery job(s), found ${String(completedJobs.length)}.`);
|
|
4803
|
+
}
|
|
4804
|
+
if (!options.allowFailedJobs && failedJobs.length > 0) {
|
|
4805
|
+
issues.push(`${String(failedJobs.length)} recent recovery job(s) failed.`);
|
|
4806
|
+
}
|
|
4807
|
+
if (runningJobs.length > 0) {
|
|
4808
|
+
const message = `${String(runningJobs.length)} recovery job(s) are still queued or running.`;
|
|
4809
|
+
if (options.failOnRunningJobs) {
|
|
4810
|
+
issues.push(message);
|
|
4811
|
+
} else {
|
|
4812
|
+
warnings.push(message);
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
const status = issues.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass";
|
|
4816
|
+
const detail = status === "pass" ? `${String(completedJobs.length)} completed recovery job(s), ${String(failedJobs.length)} failed, ${String(runningJobs.length)} running.` : [...issues, ...warnings].join(" ");
|
|
4817
|
+
return {
|
|
4818
|
+
actions: [
|
|
4819
|
+
{
|
|
4820
|
+
description: "Open the recovery job UI and run or inspect proof repair jobs.",
|
|
4821
|
+
href,
|
|
4822
|
+
label: "Open recovery job history"
|
|
4823
|
+
},
|
|
4824
|
+
{
|
|
4825
|
+
description: "Open the recovery job history API.",
|
|
4826
|
+
href: sourceHref,
|
|
4827
|
+
label: "Open recovery jobs API"
|
|
4828
|
+
}
|
|
4829
|
+
],
|
|
4830
|
+
detail,
|
|
4831
|
+
gateExplanation: {
|
|
4832
|
+
evidenceHref: sourceHref,
|
|
4833
|
+
observed: completedJobs.length,
|
|
4834
|
+
remediation: "Run a browser or phone recovery proof job and ensure recent recovery jobs complete without failure.",
|
|
4835
|
+
sourceHref,
|
|
4836
|
+
threshold: minCompletedJobs,
|
|
4837
|
+
thresholdLabel: "Minimum completed recovery jobs",
|
|
4838
|
+
unit: "count"
|
|
4839
|
+
},
|
|
4840
|
+
href,
|
|
4841
|
+
label,
|
|
4842
|
+
proofSource: {
|
|
4843
|
+
href: sourceHref,
|
|
4844
|
+
source: "recovery-job-store",
|
|
4845
|
+
sourceLabel: "Real-call recovery jobs"
|
|
4846
|
+
},
|
|
4847
|
+
status,
|
|
4848
|
+
value: `${String(completedJobs.length)} completed / ${String(failedJobs.length)} failed / ${String(runningJobs.length)} running`
|
|
4849
|
+
};
|
|
4850
|
+
};
|
|
4751
4851
|
var resolveVoiceRealCallProfileProviderRoute = (options) => {
|
|
4752
4852
|
const defaults = readRealCallProfileDefaultsReport(options.defaults);
|
|
4753
4853
|
const profile = defaults.profiles.find((item) => item.profileId === options.profileId) ?? defaults.profiles.find((item) => item.status === "pass") ?? defaults.profiles[0];
|
|
@@ -5448,6 +5548,32 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
|
|
|
5448
5548
|
};
|
|
5449
5549
|
};
|
|
5450
5550
|
routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
|
|
5551
|
+
routes.get(`${path}/actions/jobs`, async ({
|
|
5552
|
+
query,
|
|
5553
|
+
set
|
|
5554
|
+
}) => {
|
|
5555
|
+
if (!options.jobStore?.list) {
|
|
5556
|
+
set.status = 501;
|
|
5557
|
+
return Response.json({
|
|
5558
|
+
jobs: [],
|
|
5559
|
+
message: "No real-call profile recovery job list store is configured.",
|
|
5560
|
+
ok: false
|
|
5561
|
+
}, { headers: options.headers });
|
|
5562
|
+
}
|
|
5563
|
+
const actionId = Object.keys(realCallProfileActionPaths).includes(query.actionId ?? "") ? query.actionId : undefined;
|
|
5564
|
+
const status = ["fail", "pass", "queued", "running"].includes(query.status ?? "") ? query.status : undefined;
|
|
5565
|
+
const limit = Number(query.limit);
|
|
5566
|
+
const jobs = await options.jobStore.list({
|
|
5567
|
+
actionId,
|
|
5568
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : undefined,
|
|
5569
|
+
status
|
|
5570
|
+
});
|
|
5571
|
+
return Response.json({
|
|
5572
|
+
generatedAt: new Date().toISOString(),
|
|
5573
|
+
jobs,
|
|
5574
|
+
ok: true
|
|
5575
|
+
}, { headers: options.headers });
|
|
5576
|
+
});
|
|
5451
5577
|
routes.get(`${path}/actions/:jobId`, async ({
|
|
5452
5578
|
params,
|
|
5453
5579
|
set
|
package/dist/index.d.ts
CHANGED
|
@@ -30,12 +30,12 @@ export { assertVoicePlatformCoverage, buildVoicePlatformCoverageSummary, createV
|
|
|
30
30
|
export { assertVoiceCompetitiveCoverage, buildVoiceCompetitiveCoverageReport, createVoiceCompetitiveCoverageRoutes, evaluateVoiceCompetitiveCoverage, renderVoiceCompetitiveCoverageHTML, renderVoiceCompetitiveCoverageMarkdown } from './competitiveCoverage';
|
|
31
31
|
export type { VoiceCompetitiveCoverageAssertionInput, VoiceCompetitiveCoverageAssertionReport, VoiceCompetitiveCoverageIssue, VoiceCompetitiveCoverageLevel, VoiceCompetitiveCoverageReport, VoiceCompetitiveCoverageReportInput, VoiceCompetitiveCoverageRoutesOptions, VoiceCompetitiveCoverageStatus, VoiceCompetitiveCoverageSummary, VoiceCompetitiveDepthLevel, VoiceCompetitiveEvidence, VoiceCompetitiveSurface } from './competitiveCoverage';
|
|
32
32
|
export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertionReport, VoicePlatformCoverageEvidence, VoicePlatformCoverageRoutesOptions, VoicePlatformCoverageStatus, VoicePlatformCoverageSummary, VoicePlatformCoverageSummaryInput, VoicePlatformCoverageSurface } from './platformCoverage';
|
|
33
|
-
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
33
|
+
export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryJobHistoryCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
34
34
|
export { applyVoiceProfileSwitchGuard, buildVoiceProfileSwitchReadinessReport, buildVoiceProfileSwitchLiveDecisionReport, createVoiceProfileSwitchLiveDecisionRoutes, createVoiceProfileSwitchPolicyProofRoutes, createVoiceProfileSwitchReadinessRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchLiveDecisionHTML, renderVoiceProfileSwitchPolicyProofHTML, renderVoiceProfileSwitchReadinessHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
|
|
35
35
|
export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchLiveDecisionEvidence, VoiceProfileSwitchLiveDecisionReport, VoiceProfileSwitchLiveDecisionReportOptions, VoiceProfileSwitchLiveDecisionRoutesOptions, VoiceProfileSwitchLiveDecisionSession, VoiceProfileSwitchPolicyProofCase, VoiceProfileSwitchPolicyProofCaseResult, VoiceProfileSwitchPolicyProofOptions, VoiceProfileSwitchPolicyProofReport, VoiceProfileSwitchPolicyProofRoutesOptions, VoiceProfileSwitchReadinessIssue, VoiceProfileSwitchReadinessOptions, VoiceProfileSwitchReadinessReport, VoiceProfileSwitchReadinessRoutesOptions, VoiceProfileSwitchReadinessStatus, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
|
|
36
36
|
export { buildVoiceProviderDecisionTraceReport, createVoiceProviderDecisionTraceEvent, createVoiceProviderDecisionTraceRoutes, listVoiceProviderDecisionTraces, renderVoiceProviderDecisionTraceHTML, renderVoiceProviderDecisionTraceMarkdown } from './providerDecisionTraces';
|
|
37
37
|
export type { VoiceProviderDecisionStatus, VoiceProviderDecisionSurfaceReport, VoiceProviderDecisionTrace, VoiceProviderDecisionTraceInput, VoiceProviderDecisionTraceIssue, VoiceProviderDecisionTraceReport, VoiceProviderDecisionTraceReportOptions, VoiceProviderDecisionTraceRoutesOptions } from './providerDecisionTraces';
|
|
38
|
-
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileRecoveryJob, VoiceRealCallProfileRecoveryJobCreateInput, VoiceRealCallProfileRecoveryJobStatus, VoiceRealCallProfileRecoveryJobStore, VoiceRealCallProfileRecoveryJobUpdate, VoiceSQLiteRealCallProfileRecoveryJobStoreOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
|
|
38
|
+
export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryJobHistoryCheckOptions, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileRecoveryJob, VoiceRealCallProfileRecoveryJobCreateInput, VoiceRealCallProfileRecoveryJobListOptions, VoiceRealCallProfileRecoveryJobStatus, VoiceRealCallProfileRecoveryJobStore, VoiceRealCallProfileRecoveryJobUpdate, VoiceSQLiteRealCallProfileRecoveryJobStoreOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
|
|
39
39
|
export { assertVoiceSloCalibration, buildVoiceSloCalibrationReport, buildVoiceSloReadinessThresholdReport, createVoiceSloReadinessThresholdOptions, createVoiceSloReadinessThresholdRoutes, createVoiceSloThresholdProfile, createVoiceSloCalibrationRoutes, renderVoiceSloCalibrationMarkdown, renderVoiceSloReadinessThresholdHTML, renderVoiceSloReadinessThresholdMarkdown } from './sloCalibration';
|
|
40
40
|
export type { VoiceSloCalibrationMetricKey, VoiceSloCalibrationOptions, VoiceSloCalibrationReport, VoiceSloCalibrationRoutesOptions, VoiceSloCalibrationSample, VoiceSloCalibrationStatus, VoiceSloCalibrationThreshold, VoiceSloCalibrationThresholds, VoiceSloReadinessThresholdReport, VoiceSloReadinessThresholdReportOptions, VoiceSloReadinessThresholdOptions, VoiceSloReadinessThresholdRoutesOptions, VoiceSloThresholdProfile } from './sloCalibration';
|
|
41
41
|
export { assertVoiceLiveOpsControlEvidence, assertVoiceLiveOpsEvidence, buildVoiceLiveOpsControlState, createVoiceLiveOpsController, createVoiceLiveOpsRoutes, createVoiceMemoryLiveOpsControlStore, evaluateVoiceLiveOpsControlEvidence, evaluateVoiceLiveOpsEvidence, getVoiceLiveOpsControlStatus, VOICE_LIVE_OPS_ACTIONS } from './liveOps';
|
package/dist/index.js
CHANGED
|
@@ -16118,6 +16118,13 @@ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
16118
16118
|
get(id) {
|
|
16119
16119
|
return jobs.get(id);
|
|
16120
16120
|
},
|
|
16121
|
+
list(input = {}) {
|
|
16122
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
16123
|
+
return [...jobs.values()].filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).sort((first, second) => {
|
|
16124
|
+
const updatedDelta = Date.parse(second.updatedAt) - Date.parse(first.updatedAt);
|
|
16125
|
+
return updatedDelta === 0 ? second.id.localeCompare(first.id) : updatedDelta;
|
|
16126
|
+
}).slice(0, limit);
|
|
16127
|
+
},
|
|
16121
16128
|
update(id, update) {
|
|
16122
16129
|
const existing = jobs.get(id);
|
|
16123
16130
|
if (!existing) {
|
|
@@ -16151,6 +16158,7 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
16151
16158
|
payload TEXT NOT NULL
|
|
16152
16159
|
)`);
|
|
16153
16160
|
const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
|
|
16161
|
+
const listStatement = database.query(`SELECT payload FROM "${tableName}" ORDER BY sort_at DESC, id DESC`);
|
|
16154
16162
|
const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
|
|
16155
16163
|
VALUES (?1, ?2, ?3)
|
|
16156
16164
|
ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
|
|
@@ -16177,6 +16185,10 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
16177
16185
|
get(id) {
|
|
16178
16186
|
return readJob(id);
|
|
16179
16187
|
},
|
|
16188
|
+
list(input = {}) {
|
|
16189
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
16190
|
+
return listStatement.all().map((row) => JSON.parse(row.payload)).filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).slice(0, limit);
|
|
16191
|
+
},
|
|
16180
16192
|
update(id, update) {
|
|
16181
16193
|
const existing = readJob(id);
|
|
16182
16194
|
if (!existing) {
|
|
@@ -16217,6 +16229,94 @@ var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
|
|
|
16217
16229
|
value: `${String(report.defaults.summary.actionableProfiles)}/${String(report.summary.profileCount)} actionable`
|
|
16218
16230
|
};
|
|
16219
16231
|
};
|
|
16232
|
+
var buildVoiceRealCallProfileRecoveryJobHistoryCheck = async (store, options = {}) => {
|
|
16233
|
+
const href = options.href ?? "/voice/real-call-profile-recovery";
|
|
16234
|
+
const sourceHref = options.sourceHref ?? "/api/voice/real-call-profile-history/actions/jobs";
|
|
16235
|
+
const minCompletedJobs = options.minCompletedJobs ?? 1;
|
|
16236
|
+
const label = options.label ?? "Real-call recovery job history";
|
|
16237
|
+
if (!store?.list) {
|
|
16238
|
+
return {
|
|
16239
|
+
actions: [
|
|
16240
|
+
{
|
|
16241
|
+
description: "Configure a recovery job store with list support so operators can inspect proof repair history.",
|
|
16242
|
+
href,
|
|
16243
|
+
label: "Configure recovery job history"
|
|
16244
|
+
}
|
|
16245
|
+
],
|
|
16246
|
+
detail: "No real-call profile recovery job store with list support is configured.",
|
|
16247
|
+
gateExplanation: {
|
|
16248
|
+
evidenceHref: sourceHref,
|
|
16249
|
+
observed: "missing",
|
|
16250
|
+
remediation: "Use the bundled memory or SQLite recovery job store, or implement list() on the custom store.",
|
|
16251
|
+
sourceHref,
|
|
16252
|
+
threshold: "list support",
|
|
16253
|
+
thresholdLabel: "Inspectable recovery jobs",
|
|
16254
|
+
unit: "status"
|
|
16255
|
+
},
|
|
16256
|
+
href,
|
|
16257
|
+
label,
|
|
16258
|
+
status: "fail",
|
|
16259
|
+
value: "missing"
|
|
16260
|
+
};
|
|
16261
|
+
}
|
|
16262
|
+
const jobs = await store.list({ limit: 50 });
|
|
16263
|
+
const now = Date.now();
|
|
16264
|
+
const recentJobs = options.maxAgeMs === undefined ? jobs : jobs.filter((job) => now - Date.parse(job.updatedAt) <= options.maxAgeMs);
|
|
16265
|
+
const completedJobs = recentJobs.filter((job) => job.status === "pass" || job.status === "fail");
|
|
16266
|
+
const failedJobs = recentJobs.filter((job) => job.status === "fail");
|
|
16267
|
+
const runningJobs = recentJobs.filter((job) => job.status === "queued" || job.status === "running");
|
|
16268
|
+
const issues = [];
|
|
16269
|
+
const warnings = [];
|
|
16270
|
+
if (completedJobs.length < minCompletedJobs) {
|
|
16271
|
+
issues.push(`Expected at least ${String(minCompletedJobs)} completed recovery job(s), found ${String(completedJobs.length)}.`);
|
|
16272
|
+
}
|
|
16273
|
+
if (!options.allowFailedJobs && failedJobs.length > 0) {
|
|
16274
|
+
issues.push(`${String(failedJobs.length)} recent recovery job(s) failed.`);
|
|
16275
|
+
}
|
|
16276
|
+
if (runningJobs.length > 0) {
|
|
16277
|
+
const message = `${String(runningJobs.length)} recovery job(s) are still queued or running.`;
|
|
16278
|
+
if (options.failOnRunningJobs) {
|
|
16279
|
+
issues.push(message);
|
|
16280
|
+
} else {
|
|
16281
|
+
warnings.push(message);
|
|
16282
|
+
}
|
|
16283
|
+
}
|
|
16284
|
+
const status = issues.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass";
|
|
16285
|
+
const detail = status === "pass" ? `${String(completedJobs.length)} completed recovery job(s), ${String(failedJobs.length)} failed, ${String(runningJobs.length)} running.` : [...issues, ...warnings].join(" ");
|
|
16286
|
+
return {
|
|
16287
|
+
actions: [
|
|
16288
|
+
{
|
|
16289
|
+
description: "Open the recovery job UI and run or inspect proof repair jobs.",
|
|
16290
|
+
href,
|
|
16291
|
+
label: "Open recovery job history"
|
|
16292
|
+
},
|
|
16293
|
+
{
|
|
16294
|
+
description: "Open the recovery job history API.",
|
|
16295
|
+
href: sourceHref,
|
|
16296
|
+
label: "Open recovery jobs API"
|
|
16297
|
+
}
|
|
16298
|
+
],
|
|
16299
|
+
detail,
|
|
16300
|
+
gateExplanation: {
|
|
16301
|
+
evidenceHref: sourceHref,
|
|
16302
|
+
observed: completedJobs.length,
|
|
16303
|
+
remediation: "Run a browser or phone recovery proof job and ensure recent recovery jobs complete without failure.",
|
|
16304
|
+
sourceHref,
|
|
16305
|
+
threshold: minCompletedJobs,
|
|
16306
|
+
thresholdLabel: "Minimum completed recovery jobs",
|
|
16307
|
+
unit: "count"
|
|
16308
|
+
},
|
|
16309
|
+
href,
|
|
16310
|
+
label,
|
|
16311
|
+
proofSource: {
|
|
16312
|
+
href: sourceHref,
|
|
16313
|
+
source: "recovery-job-store",
|
|
16314
|
+
sourceLabel: "Real-call recovery jobs"
|
|
16315
|
+
},
|
|
16316
|
+
status,
|
|
16317
|
+
value: `${String(completedJobs.length)} completed / ${String(failedJobs.length)} failed / ${String(runningJobs.length)} running`
|
|
16318
|
+
};
|
|
16319
|
+
};
|
|
16220
16320
|
var resolveVoiceRealCallProfileProviderRoute = (options) => {
|
|
16221
16321
|
const defaults = readRealCallProfileDefaultsReport(options.defaults);
|
|
16222
16322
|
const profile = defaults.profiles.find((item) => item.profileId === options.profileId) ?? defaults.profiles.find((item) => item.status === "pass") ?? defaults.profiles[0];
|
|
@@ -16917,6 +17017,32 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
|
|
|
16917
17017
|
};
|
|
16918
17018
|
};
|
|
16919
17019
|
routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
|
|
17020
|
+
routes.get(`${path}/actions/jobs`, async ({
|
|
17021
|
+
query,
|
|
17022
|
+
set
|
|
17023
|
+
}) => {
|
|
17024
|
+
if (!options.jobStore?.list) {
|
|
17025
|
+
set.status = 501;
|
|
17026
|
+
return Response.json({
|
|
17027
|
+
jobs: [],
|
|
17028
|
+
message: "No real-call profile recovery job list store is configured.",
|
|
17029
|
+
ok: false
|
|
17030
|
+
}, { headers: options.headers });
|
|
17031
|
+
}
|
|
17032
|
+
const actionId = Object.keys(realCallProfileActionPaths).includes(query.actionId ?? "") ? query.actionId : undefined;
|
|
17033
|
+
const status = ["fail", "pass", "queued", "running"].includes(query.status ?? "") ? query.status : undefined;
|
|
17034
|
+
const limit = Number(query.limit);
|
|
17035
|
+
const jobs = await options.jobStore.list({
|
|
17036
|
+
actionId,
|
|
17037
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : undefined,
|
|
17038
|
+
status
|
|
17039
|
+
});
|
|
17040
|
+
return Response.json({
|
|
17041
|
+
generatedAt: new Date().toISOString(),
|
|
17042
|
+
jobs,
|
|
17043
|
+
ok: true
|
|
17044
|
+
}, { headers: options.headers });
|
|
17045
|
+
});
|
|
16920
17046
|
routes.get(`${path}/actions/:jobId`, async ({
|
|
16921
17047
|
params,
|
|
16922
17048
|
set
|
|
@@ -39236,6 +39362,7 @@ export {
|
|
|
39236
39362
|
buildVoiceRealtimeProviderContractMatrix,
|
|
39237
39363
|
buildVoiceRealtimeChannelRuntimeSamplesFromTrace,
|
|
39238
39364
|
buildVoiceRealtimeChannelReport,
|
|
39365
|
+
buildVoiceRealCallProfileRecoveryJobHistoryCheck,
|
|
39239
39366
|
buildVoiceRealCallProfileRecoveryActions,
|
|
39240
39367
|
buildVoiceRealCallProfileReadinessCheck,
|
|
39241
39368
|
buildVoiceRealCallProfileHistoryReport,
|
package/dist/proofTrends.d.ts
CHANGED
|
@@ -378,6 +378,15 @@ export type VoiceRealCallProfileReadinessCheckOptions = {
|
|
|
378
378
|
sourceHref?: string;
|
|
379
379
|
};
|
|
380
380
|
export type VoiceRealCallProfileRecoveryActionOptions = VoiceRealCallProfileReadinessCheckOptions;
|
|
381
|
+
export type VoiceRealCallProfileRecoveryJobHistoryCheckOptions = {
|
|
382
|
+
allowFailedJobs?: boolean;
|
|
383
|
+
failOnRunningJobs?: boolean;
|
|
384
|
+
href?: string;
|
|
385
|
+
label?: string;
|
|
386
|
+
maxAgeMs?: number;
|
|
387
|
+
minCompletedJobs?: number;
|
|
388
|
+
sourceHref?: string;
|
|
389
|
+
};
|
|
381
390
|
export type VoiceRealCallProfileRecoveryActionHandlerInput = {
|
|
382
391
|
actionId: VoiceRealCallProfileRecoveryActionId;
|
|
383
392
|
report: VoiceRealCallProfileHistoryReport;
|
|
@@ -414,9 +423,15 @@ export type VoiceRealCallProfileRecoveryJobCreateInput = {
|
|
|
414
423
|
status?: VoiceRealCallProfileRecoveryJobStatus;
|
|
415
424
|
};
|
|
416
425
|
export type VoiceRealCallProfileRecoveryJobUpdate = Partial<Pick<VoiceRealCallProfileRecoveryJob, 'completedAt' | 'message' | 'ok' | 'report' | 'startedAt' | 'status' | 'updatedAt'>>;
|
|
426
|
+
export type VoiceRealCallProfileRecoveryJobListOptions = {
|
|
427
|
+
actionId?: VoiceRealCallProfileRecoveryActionId;
|
|
428
|
+
limit?: number;
|
|
429
|
+
status?: VoiceRealCallProfileRecoveryJobStatus;
|
|
430
|
+
};
|
|
417
431
|
export type VoiceRealCallProfileRecoveryJobStore = {
|
|
418
432
|
create(input: VoiceRealCallProfileRecoveryJobCreateInput): Promise<VoiceRealCallProfileRecoveryJob> | VoiceRealCallProfileRecoveryJob;
|
|
419
433
|
get(id: string): Promise<VoiceRealCallProfileRecoveryJob | undefined> | VoiceRealCallProfileRecoveryJob | undefined;
|
|
434
|
+
list?(options?: VoiceRealCallProfileRecoveryJobListOptions): Promise<VoiceRealCallProfileRecoveryJob[]> | VoiceRealCallProfileRecoveryJob[];
|
|
420
435
|
update(id: string, update: VoiceRealCallProfileRecoveryJobUpdate): Promise<VoiceRealCallProfileRecoveryJob | undefined> | VoiceRealCallProfileRecoveryJob | undefined;
|
|
421
436
|
};
|
|
422
437
|
export type VoiceSQLiteRealCallProfileRecoveryJobStoreOptions = {
|
|
@@ -472,6 +487,7 @@ export declare const createVoiceInMemoryRealCallProfileRecoveryJobStore: (option
|
|
|
472
487
|
}) => VoiceRealCallProfileRecoveryJobStore;
|
|
473
488
|
export declare const createVoiceSQLiteRealCallProfileRecoveryJobStore: (options?: VoiceSQLiteRealCallProfileRecoveryJobStoreOptions) => VoiceRealCallProfileRecoveryJobStore;
|
|
474
489
|
export declare const buildVoiceRealCallProfileReadinessCheck: (report: VoiceRealCallProfileHistoryReport, options?: VoiceRealCallProfileReadinessCheckOptions) => VoiceProductionReadinessCheck;
|
|
490
|
+
export declare const buildVoiceRealCallProfileRecoveryJobHistoryCheck: (store: Pick<VoiceRealCallProfileRecoveryJobStore, "list"> | undefined, options?: VoiceRealCallProfileRecoveryJobHistoryCheckOptions) => Promise<VoiceProductionReadinessCheck>;
|
|
475
491
|
export declare const resolveVoiceRealCallProfileProviderRoute: <TProvider extends string = string>(options: VoiceRealCallProfileProviderRouteOptions<TProvider>) => TProvider | undefined;
|
|
476
492
|
export declare const buildVoiceRealCallProfileHistoryReport: (options?: VoiceRealCallProfileHistoryOptions) => VoiceRealCallProfileHistoryReport;
|
|
477
493
|
export declare const evaluateVoiceProofTrendEvidence: (report: VoiceProofTrendReport, input?: VoiceProofTrendAssertionInput) => VoiceProofTrendAssertionReport;
|
package/dist/react/index.js
CHANGED
|
@@ -2236,6 +2236,13 @@ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
2236
2236
|
get(id) {
|
|
2237
2237
|
return jobs.get(id);
|
|
2238
2238
|
},
|
|
2239
|
+
list(input = {}) {
|
|
2240
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
2241
|
+
return [...jobs.values()].filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).sort((first, second) => {
|
|
2242
|
+
const updatedDelta = Date.parse(second.updatedAt) - Date.parse(first.updatedAt);
|
|
2243
|
+
return updatedDelta === 0 ? second.id.localeCompare(first.id) : updatedDelta;
|
|
2244
|
+
}).slice(0, limit);
|
|
2245
|
+
},
|
|
2239
2246
|
update(id, update) {
|
|
2240
2247
|
const existing = jobs.get(id);
|
|
2241
2248
|
if (!existing) {
|
|
@@ -2269,6 +2276,7 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
2269
2276
|
payload TEXT NOT NULL
|
|
2270
2277
|
)`);
|
|
2271
2278
|
const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
|
|
2279
|
+
const listStatement = database.query(`SELECT payload FROM "${tableName}" ORDER BY sort_at DESC, id DESC`);
|
|
2272
2280
|
const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
|
|
2273
2281
|
VALUES (?1, ?2, ?3)
|
|
2274
2282
|
ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
|
|
@@ -2295,6 +2303,10 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
2295
2303
|
get(id) {
|
|
2296
2304
|
return readJob(id);
|
|
2297
2305
|
},
|
|
2306
|
+
list(input = {}) {
|
|
2307
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
2308
|
+
return listStatement.all().map((row) => JSON.parse(row.payload)).filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).slice(0, limit);
|
|
2309
|
+
},
|
|
2298
2310
|
update(id, update) {
|
|
2299
2311
|
const existing = readJob(id);
|
|
2300
2312
|
if (!existing) {
|
|
@@ -2335,6 +2347,94 @@ var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
|
|
|
2335
2347
|
value: `${String(report.defaults.summary.actionableProfiles)}/${String(report.summary.profileCount)} actionable`
|
|
2336
2348
|
};
|
|
2337
2349
|
};
|
|
2350
|
+
var buildVoiceRealCallProfileRecoveryJobHistoryCheck = async (store, options = {}) => {
|
|
2351
|
+
const href = options.href ?? "/voice/real-call-profile-recovery";
|
|
2352
|
+
const sourceHref = options.sourceHref ?? "/api/voice/real-call-profile-history/actions/jobs";
|
|
2353
|
+
const minCompletedJobs = options.minCompletedJobs ?? 1;
|
|
2354
|
+
const label = options.label ?? "Real-call recovery job history";
|
|
2355
|
+
if (!store?.list) {
|
|
2356
|
+
return {
|
|
2357
|
+
actions: [
|
|
2358
|
+
{
|
|
2359
|
+
description: "Configure a recovery job store with list support so operators can inspect proof repair history.",
|
|
2360
|
+
href,
|
|
2361
|
+
label: "Configure recovery job history"
|
|
2362
|
+
}
|
|
2363
|
+
],
|
|
2364
|
+
detail: "No real-call profile recovery job store with list support is configured.",
|
|
2365
|
+
gateExplanation: {
|
|
2366
|
+
evidenceHref: sourceHref,
|
|
2367
|
+
observed: "missing",
|
|
2368
|
+
remediation: "Use the bundled memory or SQLite recovery job store, or implement list() on the custom store.",
|
|
2369
|
+
sourceHref,
|
|
2370
|
+
threshold: "list support",
|
|
2371
|
+
thresholdLabel: "Inspectable recovery jobs",
|
|
2372
|
+
unit: "status"
|
|
2373
|
+
},
|
|
2374
|
+
href,
|
|
2375
|
+
label,
|
|
2376
|
+
status: "fail",
|
|
2377
|
+
value: "missing"
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
const jobs = await store.list({ limit: 50 });
|
|
2381
|
+
const now = Date.now();
|
|
2382
|
+
const recentJobs = options.maxAgeMs === undefined ? jobs : jobs.filter((job) => now - Date.parse(job.updatedAt) <= options.maxAgeMs);
|
|
2383
|
+
const completedJobs = recentJobs.filter((job) => job.status === "pass" || job.status === "fail");
|
|
2384
|
+
const failedJobs = recentJobs.filter((job) => job.status === "fail");
|
|
2385
|
+
const runningJobs = recentJobs.filter((job) => job.status === "queued" || job.status === "running");
|
|
2386
|
+
const issues = [];
|
|
2387
|
+
const warnings = [];
|
|
2388
|
+
if (completedJobs.length < minCompletedJobs) {
|
|
2389
|
+
issues.push(`Expected at least ${String(minCompletedJobs)} completed recovery job(s), found ${String(completedJobs.length)}.`);
|
|
2390
|
+
}
|
|
2391
|
+
if (!options.allowFailedJobs && failedJobs.length > 0) {
|
|
2392
|
+
issues.push(`${String(failedJobs.length)} recent recovery job(s) failed.`);
|
|
2393
|
+
}
|
|
2394
|
+
if (runningJobs.length > 0) {
|
|
2395
|
+
const message = `${String(runningJobs.length)} recovery job(s) are still queued or running.`;
|
|
2396
|
+
if (options.failOnRunningJobs) {
|
|
2397
|
+
issues.push(message);
|
|
2398
|
+
} else {
|
|
2399
|
+
warnings.push(message);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
const status = issues.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass";
|
|
2403
|
+
const detail = status === "pass" ? `${String(completedJobs.length)} completed recovery job(s), ${String(failedJobs.length)} failed, ${String(runningJobs.length)} running.` : [...issues, ...warnings].join(" ");
|
|
2404
|
+
return {
|
|
2405
|
+
actions: [
|
|
2406
|
+
{
|
|
2407
|
+
description: "Open the recovery job UI and run or inspect proof repair jobs.",
|
|
2408
|
+
href,
|
|
2409
|
+
label: "Open recovery job history"
|
|
2410
|
+
},
|
|
2411
|
+
{
|
|
2412
|
+
description: "Open the recovery job history API.",
|
|
2413
|
+
href: sourceHref,
|
|
2414
|
+
label: "Open recovery jobs API"
|
|
2415
|
+
}
|
|
2416
|
+
],
|
|
2417
|
+
detail,
|
|
2418
|
+
gateExplanation: {
|
|
2419
|
+
evidenceHref: sourceHref,
|
|
2420
|
+
observed: completedJobs.length,
|
|
2421
|
+
remediation: "Run a browser or phone recovery proof job and ensure recent recovery jobs complete without failure.",
|
|
2422
|
+
sourceHref,
|
|
2423
|
+
threshold: minCompletedJobs,
|
|
2424
|
+
thresholdLabel: "Minimum completed recovery jobs",
|
|
2425
|
+
unit: "count"
|
|
2426
|
+
},
|
|
2427
|
+
href,
|
|
2428
|
+
label,
|
|
2429
|
+
proofSource: {
|
|
2430
|
+
href: sourceHref,
|
|
2431
|
+
source: "recovery-job-store",
|
|
2432
|
+
sourceLabel: "Real-call recovery jobs"
|
|
2433
|
+
},
|
|
2434
|
+
status,
|
|
2435
|
+
value: `${String(completedJobs.length)} completed / ${String(failedJobs.length)} failed / ${String(runningJobs.length)} running`
|
|
2436
|
+
};
|
|
2437
|
+
};
|
|
2338
2438
|
var resolveVoiceRealCallProfileProviderRoute = (options) => {
|
|
2339
2439
|
const defaults = readRealCallProfileDefaultsReport(options.defaults);
|
|
2340
2440
|
const profile = defaults.profiles.find((item) => item.profileId === options.profileId) ?? defaults.profiles.find((item) => item.status === "pass") ?? defaults.profiles[0];
|
|
@@ -3035,6 +3135,32 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
|
|
|
3035
3135
|
};
|
|
3036
3136
|
};
|
|
3037
3137
|
routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
|
|
3138
|
+
routes.get(`${path}/actions/jobs`, async ({
|
|
3139
|
+
query,
|
|
3140
|
+
set
|
|
3141
|
+
}) => {
|
|
3142
|
+
if (!options.jobStore?.list) {
|
|
3143
|
+
set.status = 501;
|
|
3144
|
+
return Response.json({
|
|
3145
|
+
jobs: [],
|
|
3146
|
+
message: "No real-call profile recovery job list store is configured.",
|
|
3147
|
+
ok: false
|
|
3148
|
+
}, { headers: options.headers });
|
|
3149
|
+
}
|
|
3150
|
+
const actionId = Object.keys(realCallProfileActionPaths).includes(query.actionId ?? "") ? query.actionId : undefined;
|
|
3151
|
+
const status = ["fail", "pass", "queued", "running"].includes(query.status ?? "") ? query.status : undefined;
|
|
3152
|
+
const limit = Number(query.limit);
|
|
3153
|
+
const jobs = await options.jobStore.list({
|
|
3154
|
+
actionId,
|
|
3155
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : undefined,
|
|
3156
|
+
status
|
|
3157
|
+
});
|
|
3158
|
+
return Response.json({
|
|
3159
|
+
generatedAt: new Date().toISOString(),
|
|
3160
|
+
jobs,
|
|
3161
|
+
ok: true
|
|
3162
|
+
}, { headers: options.headers });
|
|
3163
|
+
});
|
|
3038
3164
|
routes.get(`${path}/actions/:jobId`, async ({
|
|
3039
3165
|
params,
|
|
3040
3166
|
set
|
package/dist/vue/index.js
CHANGED
|
@@ -2157,6 +2157,13 @@ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
2157
2157
|
get(id) {
|
|
2158
2158
|
return jobs.get(id);
|
|
2159
2159
|
},
|
|
2160
|
+
list(input = {}) {
|
|
2161
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
2162
|
+
return [...jobs.values()].filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).sort((first, second) => {
|
|
2163
|
+
const updatedDelta = Date.parse(second.updatedAt) - Date.parse(first.updatedAt);
|
|
2164
|
+
return updatedDelta === 0 ? second.id.localeCompare(first.id) : updatedDelta;
|
|
2165
|
+
}).slice(0, limit);
|
|
2166
|
+
},
|
|
2160
2167
|
update(id, update) {
|
|
2161
2168
|
const existing = jobs.get(id);
|
|
2162
2169
|
if (!existing) {
|
|
@@ -2190,6 +2197,7 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
2190
2197
|
payload TEXT NOT NULL
|
|
2191
2198
|
)`);
|
|
2192
2199
|
const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
|
|
2200
|
+
const listStatement = database.query(`SELECT payload FROM "${tableName}" ORDER BY sort_at DESC, id DESC`);
|
|
2193
2201
|
const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
|
|
2194
2202
|
VALUES (?1, ?2, ?3)
|
|
2195
2203
|
ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
|
|
@@ -2216,6 +2224,10 @@ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
|
|
|
2216
2224
|
get(id) {
|
|
2217
2225
|
return readJob(id);
|
|
2218
2226
|
},
|
|
2227
|
+
list(input = {}) {
|
|
2228
|
+
const limit = Number.isFinite(input.limit) && input.limit !== undefined && input.limit > 0 ? Math.floor(input.limit) : 50;
|
|
2229
|
+
return listStatement.all().map((row) => JSON.parse(row.payload)).filter((job) => !input.actionId || job.actionId === input.actionId).filter((job) => !input.status || job.status === input.status).slice(0, limit);
|
|
2230
|
+
},
|
|
2219
2231
|
update(id, update) {
|
|
2220
2232
|
const existing = readJob(id);
|
|
2221
2233
|
if (!existing) {
|
|
@@ -2256,6 +2268,94 @@ var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
|
|
|
2256
2268
|
value: `${String(report.defaults.summary.actionableProfiles)}/${String(report.summary.profileCount)} actionable`
|
|
2257
2269
|
};
|
|
2258
2270
|
};
|
|
2271
|
+
var buildVoiceRealCallProfileRecoveryJobHistoryCheck = async (store, options = {}) => {
|
|
2272
|
+
const href = options.href ?? "/voice/real-call-profile-recovery";
|
|
2273
|
+
const sourceHref = options.sourceHref ?? "/api/voice/real-call-profile-history/actions/jobs";
|
|
2274
|
+
const minCompletedJobs = options.minCompletedJobs ?? 1;
|
|
2275
|
+
const label = options.label ?? "Real-call recovery job history";
|
|
2276
|
+
if (!store?.list) {
|
|
2277
|
+
return {
|
|
2278
|
+
actions: [
|
|
2279
|
+
{
|
|
2280
|
+
description: "Configure a recovery job store with list support so operators can inspect proof repair history.",
|
|
2281
|
+
href,
|
|
2282
|
+
label: "Configure recovery job history"
|
|
2283
|
+
}
|
|
2284
|
+
],
|
|
2285
|
+
detail: "No real-call profile recovery job store with list support is configured.",
|
|
2286
|
+
gateExplanation: {
|
|
2287
|
+
evidenceHref: sourceHref,
|
|
2288
|
+
observed: "missing",
|
|
2289
|
+
remediation: "Use the bundled memory or SQLite recovery job store, or implement list() on the custom store.",
|
|
2290
|
+
sourceHref,
|
|
2291
|
+
threshold: "list support",
|
|
2292
|
+
thresholdLabel: "Inspectable recovery jobs",
|
|
2293
|
+
unit: "status"
|
|
2294
|
+
},
|
|
2295
|
+
href,
|
|
2296
|
+
label,
|
|
2297
|
+
status: "fail",
|
|
2298
|
+
value: "missing"
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
const jobs = await store.list({ limit: 50 });
|
|
2302
|
+
const now = Date.now();
|
|
2303
|
+
const recentJobs = options.maxAgeMs === undefined ? jobs : jobs.filter((job) => now - Date.parse(job.updatedAt) <= options.maxAgeMs);
|
|
2304
|
+
const completedJobs = recentJobs.filter((job) => job.status === "pass" || job.status === "fail");
|
|
2305
|
+
const failedJobs = recentJobs.filter((job) => job.status === "fail");
|
|
2306
|
+
const runningJobs = recentJobs.filter((job) => job.status === "queued" || job.status === "running");
|
|
2307
|
+
const issues = [];
|
|
2308
|
+
const warnings = [];
|
|
2309
|
+
if (completedJobs.length < minCompletedJobs) {
|
|
2310
|
+
issues.push(`Expected at least ${String(minCompletedJobs)} completed recovery job(s), found ${String(completedJobs.length)}.`);
|
|
2311
|
+
}
|
|
2312
|
+
if (!options.allowFailedJobs && failedJobs.length > 0) {
|
|
2313
|
+
issues.push(`${String(failedJobs.length)} recent recovery job(s) failed.`);
|
|
2314
|
+
}
|
|
2315
|
+
if (runningJobs.length > 0) {
|
|
2316
|
+
const message = `${String(runningJobs.length)} recovery job(s) are still queued or running.`;
|
|
2317
|
+
if (options.failOnRunningJobs) {
|
|
2318
|
+
issues.push(message);
|
|
2319
|
+
} else {
|
|
2320
|
+
warnings.push(message);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
const status = issues.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass";
|
|
2324
|
+
const detail = status === "pass" ? `${String(completedJobs.length)} completed recovery job(s), ${String(failedJobs.length)} failed, ${String(runningJobs.length)} running.` : [...issues, ...warnings].join(" ");
|
|
2325
|
+
return {
|
|
2326
|
+
actions: [
|
|
2327
|
+
{
|
|
2328
|
+
description: "Open the recovery job UI and run or inspect proof repair jobs.",
|
|
2329
|
+
href,
|
|
2330
|
+
label: "Open recovery job history"
|
|
2331
|
+
},
|
|
2332
|
+
{
|
|
2333
|
+
description: "Open the recovery job history API.",
|
|
2334
|
+
href: sourceHref,
|
|
2335
|
+
label: "Open recovery jobs API"
|
|
2336
|
+
}
|
|
2337
|
+
],
|
|
2338
|
+
detail,
|
|
2339
|
+
gateExplanation: {
|
|
2340
|
+
evidenceHref: sourceHref,
|
|
2341
|
+
observed: completedJobs.length,
|
|
2342
|
+
remediation: "Run a browser or phone recovery proof job and ensure recent recovery jobs complete without failure.",
|
|
2343
|
+
sourceHref,
|
|
2344
|
+
threshold: minCompletedJobs,
|
|
2345
|
+
thresholdLabel: "Minimum completed recovery jobs",
|
|
2346
|
+
unit: "count"
|
|
2347
|
+
},
|
|
2348
|
+
href,
|
|
2349
|
+
label,
|
|
2350
|
+
proofSource: {
|
|
2351
|
+
href: sourceHref,
|
|
2352
|
+
source: "recovery-job-store",
|
|
2353
|
+
sourceLabel: "Real-call recovery jobs"
|
|
2354
|
+
},
|
|
2355
|
+
status,
|
|
2356
|
+
value: `${String(completedJobs.length)} completed / ${String(failedJobs.length)} failed / ${String(runningJobs.length)} running`
|
|
2357
|
+
};
|
|
2358
|
+
};
|
|
2259
2359
|
var resolveVoiceRealCallProfileProviderRoute = (options) => {
|
|
2260
2360
|
const defaults = readRealCallProfileDefaultsReport(options.defaults);
|
|
2261
2361
|
const profile = defaults.profiles.find((item) => item.profileId === options.profileId) ?? defaults.profiles.find((item) => item.status === "pass") ?? defaults.profiles[0];
|
|
@@ -2956,6 +3056,32 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
|
|
|
2956
3056
|
};
|
|
2957
3057
|
};
|
|
2958
3058
|
routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
|
|
3059
|
+
routes.get(`${path}/actions/jobs`, async ({
|
|
3060
|
+
query,
|
|
3061
|
+
set
|
|
3062
|
+
}) => {
|
|
3063
|
+
if (!options.jobStore?.list) {
|
|
3064
|
+
set.status = 501;
|
|
3065
|
+
return Response.json({
|
|
3066
|
+
jobs: [],
|
|
3067
|
+
message: "No real-call profile recovery job list store is configured.",
|
|
3068
|
+
ok: false
|
|
3069
|
+
}, { headers: options.headers });
|
|
3070
|
+
}
|
|
3071
|
+
const actionId = Object.keys(realCallProfileActionPaths).includes(query.actionId ?? "") ? query.actionId : undefined;
|
|
3072
|
+
const status = ["fail", "pass", "queued", "running"].includes(query.status ?? "") ? query.status : undefined;
|
|
3073
|
+
const limit = Number(query.limit);
|
|
3074
|
+
const jobs = await options.jobStore.list({
|
|
3075
|
+
actionId,
|
|
3076
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : undefined,
|
|
3077
|
+
status
|
|
3078
|
+
});
|
|
3079
|
+
return Response.json({
|
|
3080
|
+
generatedAt: new Date().toISOString(),
|
|
3081
|
+
jobs,
|
|
3082
|
+
ok: true
|
|
3083
|
+
}, { headers: options.headers });
|
|
3084
|
+
});
|
|
2959
3085
|
routes.get(`${path}/actions/:jobId`, async ({
|
|
2960
3086
|
params,
|
|
2961
3087
|
set
|