@openwop/openwop-conformance 2.46.10 → 2.46.12

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [2.46.12] — 2026-10-16 — RFC 0245 and RFC 0247 Active
2
+
3
+ - **No assertion changes yet.** RFC 0245 (eval fixtures replay a recorded tool error as an error) is `Active`: the packed v2 `agent-eval-suite` schema gains optional `toolResponses[].isError`, the capability schema gains `agents.evalSuite.fixtureErrors`, and three gap rows change the packed `spec/v1/gaps.json`. The fixture and the two gated legs follow.
4
+ - **RFC 0247 `Active`: bounded stream lifetime.** The packed v2 surfaces gain the `resumable` query parameter on `streamRunEvents` and `streamHostEvents`, and the limits facet gains `maxStreamAgeSec`. No new leg (gap G1: a bounded close needs a long-held live run). A host that advertises nothing new, and a consumer that never sends `resumable=true`, see no change.
5
+
6
+ ## [2.46.11] — 2026-10-15 — a shared id reads the same on every run
7
+
8
+ - **Ledger reader: one requirement id recorded by two scenario files.** Each file runs in its own worker, and `readLedgerFile` kept whichever line came last, so the row's `assertions` and `scenario` belonged to the worker that finished last. openwop-app's `prompts.resolution-precedes-composition` read 9 then 5 assertions on identical results.
9
+ - The least certifiable file still decides the result and keeps its detail, so a failure in either file is never masked (unchanged).
10
+ - New: the files' assertion counts sum, as the in-worker fold already sums legs, and a tie names the first file by name.
11
+ - **What a host's next bundle shows:** rows witnessed by two files carry the summed count and a fixed scenario. No result changes.
12
+
1
13
  ## [2.46.10] — 2026-10-14 — RFC 0244 Accepted (provisional)
2
14
 
3
15
  - **No assertion changes.** RFC 0244 flips to `Accepted` (provisional) on MyndHyve's certified 2.46.9 cut, so the packed `spec/v1/gaps.json` changes (G9, G10 and G11 close).
@@ -136,8 +136,12 @@ export function recordRequirement(requirementId, disposition, detail, extras) {
136
136
  export function readLedgerFile(path) {
137
137
  if (!existsSync(path))
138
138
  return [];
139
- const rank = { 'executed-fail': 0, blocked: 1, 'executed-pass': 2, skipped: 3, inapplicable: 4 };
140
- const merged = new Map();
139
+ const rank = CERTIFIABILITY_RANK;
140
+ // Pass 1, per (id, scenario file). `<=`, not `<`: a per-`it` FOLD (2.37.0)
141
+ // appends the cumulative row after the leg rows it folded, so on an equal
142
+ // disposition the LAST line is the one carrying the summed `assertionCount`.
143
+ // For genuinely duplicate lines the two are identical and the choice is a no-op.
144
+ const perFile = new Map();
141
145
  for (const line of readFileSync(path, 'utf8').split('\n')) {
142
146
  if (line.trim() === '')
143
147
  continue;
@@ -150,15 +154,32 @@ export function readLedgerFile(path) {
150
154
  }
151
155
  if (typeof e.requirementId !== 'string' || !DISPOSITIONS.includes(e.disposition))
152
156
  continue;
153
- const prior = merged.get(e.requirementId);
154
- // `<=`, not `<`: a per-`it` FOLD (2.37.0) appends the cumulative row after
155
- // the leg rows it folded, so on an equal disposition the LAST line is the
156
- // one carrying the summed `assertionCount`. For genuinely duplicate lines
157
- // the two are identical and the choice is a no-op.
157
+ let files = perFile.get(e.requirementId);
158
+ if (files === undefined)
159
+ perFile.set(e.requirementId, (files = new Map()));
160
+ const file = e.scenarioFile ?? '';
161
+ const prior = files.get(file);
158
162
  if (prior === undefined || rank[e.disposition] <= rank[prior.disposition])
159
- merged.set(e.requirementId, e);
163
+ files.set(file, e);
160
164
  }
161
- return [...merged.values()].sort((a, b) => a.requirementId.localeCompare(b.requirementId));
165
+ // Pass 2, across files (2.46.11). Two files that witness ONE id run in
166
+ // different workers, so "last line wins" made the row's `assertionCount` and
167
+ // `scenarioFile` whichever worker finished last: the same host read 9 then 5
168
+ // assertions for `prompts.resolution-precedes-composition` with no result
169
+ // change. The least certifiable file still decides the disposition and keeps
170
+ // its detail; counts sum, as the in-worker fold sums them; and a tie names
171
+ // the first file by name, so the row is the same on every run.
172
+ const merged = [];
173
+ for (const files of perFile.values()) {
174
+ const rows = [...files.values()].sort((a, b) => rank[a.disposition] - rank[b.disposition] || (a.scenarioFile ?? '').localeCompare(b.scenarioFile ?? ''));
175
+ const winner = rows[0];
176
+ if (rows.length === 1 || !rows.some((r) => r.assertionCount !== undefined)) {
177
+ merged.push(winner);
178
+ continue;
179
+ }
180
+ merged.push({ ...winner, assertionCount: rows.reduce((n, r) => n + (r.assertionCount ?? 0), 0) });
181
+ }
182
+ return merged.sort((a, b) => a.requirementId.localeCompare(b.requirementId));
162
183
  }
163
184
  /**
164
185
  * The disposition for a requirement. **Absent resolves to `blocked`, never to a
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.46.10",
4
- "stampSha256": "89832e47efa852d9a9528dd1528aacb7b7c7e803df7d905b6be3ab3c105ecbce"
3
+ "version": "2.46.12",
4
+ "stampSha256": "fec6d911af8113b65da78d98ef8c21f80ac8bb2d0a5f6303287dd5d88409cc1a"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.46.10",
3
+ "version": "2.46.12",
4
4
  "description": "Black-box conformance suite for OpenWOP hosts (v2 current; v1 scenarios retained for the frozen v1 tree).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -56,6 +56,6 @@
56
56
  "@openwop/spec-artifacts": "file:../spec-artifacts"
57
57
  },
58
58
  "peerDependencies": {
59
- "@openwop/spec-artifacts": "2.46.10"
59
+ "@openwop/spec-artifacts": "2.46.12"
60
60
  }
61
61
  }
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
3
3
  "package": "@openwop/spec-artifacts",
4
- "version": "2.46.10",
5
- "corpusTag": "v2.46.10",
4
+ "version": "2.46.12",
5
+ "corpusTag": "v2.46.12",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
8
8
  "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
9
9
  "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
10
10
  "api/openapi.yaml": "ac6de5303872664b78ff419c035915593e01599e02835e777a0123de1baeb398",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "d432df92e9d6f9b5f507356bb055937dd5289e7b9f84b1c8ec64ac422bb36929",
13
- "api/v2/asyncapi.yaml": "c7fb02dd90ca56745a8f1dc49f8808a74bc45fc5c7e6252f3724bb273904cf1b",
14
- "api/v2/openapi.yaml": "780e4d7ba44543dd17647091837c1fa5978e658576c7c8ad6fcc105425a51b24",
12
+ "api/seams-v2.yaml": "e70dca4462c179fe7bfcc25244e481aa04c3da2a97e74d99e70c562191b9ec0b",
13
+ "api/v2/asyncapi.yaml": "964c60cadd09209e704b52fce5f68c28f7ea08e8bd9c04318c931e1969d79d64",
14
+ "api/v2/openapi.yaml": "0aa511e8f9c6b49e224b111a2afe81ef48fc92b1fc77dd6287b04f2e6597ea33",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "276fc1d51eb08e14e9127b9fd27761d7046ee33c12000f3a64a801322db8bf58",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -103,7 +103,7 @@
103
103
  "schemas/v2/a2ui-surface-delta-frame.schema.json": "6889c5da3fe7e76cb614038c0eea6774776fa640448558b258eb87fb50cb9b64",
104
104
  "schemas/v2/agent-deployment-transition.schema.json": "93a334ff042d51378c89fdd7134664271bf9592419bb5e3e00821ed06bc1904e",
105
105
  "schemas/v2/agent-deployment.schema.json": "d59b43fb62b522005ee22bc3bc53775a6777493e5e42d71812abc347625fa7fe",
106
- "schemas/v2/agent-eval-suite.schema.json": "e80439bc6873a5d9a14122f81d16f7650104018d814bd316aafd3fc8b8225604",
106
+ "schemas/v2/agent-eval-suite.schema.json": "8b6dac5c6485c1fc4b7228efc0a87fa5c64fce89ddd228d4f92786b1b63e37b3",
107
107
  "schemas/v2/agent-inventory-response.schema.json": "5f74c3da946dfc3114624567a989717748081ab947b4d2af3f49034d67f4306b",
108
108
  "schemas/v2/agent-manifest.schema.json": "004ff150e955deebb9e64273de5093f5144b9b3895b09953e65aa74b61140778",
109
109
  "schemas/v2/agent-org-chart.schema.json": "113b079e1dce95908467cbef7f83e076fdb82aab5b0678fc1a43e32bfc9cbadf",
@@ -117,7 +117,7 @@
117
117
  "schemas/v2/artifact.schema.json": "85b9dc284d39b99d2a66fa8cbf47f2dd3d8526d3bd6cc411ad315b372de38975",
118
118
  "schemas/v2/audit-verify-result.schema.json": "14af8e9283510f0a40e48a2059c929ee44e0fc9fa3cc66a95bfbe90b17c48297",
119
119
  "schemas/v2/budget-policy.schema.json": "1de68095db0beb5c7a4be629de26e79f62ebbe748a88d46f36e768edfd1f0d68",
120
- "schemas/v2/capabilities.schema.json": "a717d34d98201cefe1769421c468d22e124d1a46881f60683cb6e103843dbc9a",
120
+ "schemas/v2/capabilities.schema.json": "87faea080e8043abd16cba19dece777a810731fc3e4455c96c02d0bfd0997d84",
121
121
  "schemas/v2/certification-bundle.schema.json": "5cfa7fb5247160333e33a58b60d42bcd4e5269d8391d6a112d301e14828892ed",
122
122
  "schemas/v2/channel-presence-payload.schema.json": "fc03df0c20cc12a25f7ee6a47503a4c5931d79fe3d5ff24815e8fd7c1bcb9a87",
123
123
  "schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
@@ -208,14 +208,14 @@
208
208
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
209
209
  "spec/v1/alias-detectors.json": "22444c22bb88c568339ef512553f9fc38146e22b566d11ab8608f655d688d19c",
210
210
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
211
- "spec/v1/core-standard-manifest.json": "ddfa3fb570b38ffa11ed013fcf56ee6ce6639f80030460a4589dc7de105da292",
211
+ "spec/v1/core-standard-manifest.json": "920cf7ccccfb66f6438fc72a477166d3757c5c6ca6e26d7d247f0a3b801334a0",
212
212
  "spec/v1/deprecations.json": "e723bc11d7272c71789ad1303bb7f21809a8a024abf57594f9d5c35dc199ece9",
213
213
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
214
214
  "spec/v1/end-of-support.json": "35f0bf3b70fe78b16e2a2fd81a153fc27e703f6c16376c6fe7cdf1f4f2c4e6d4",
215
215
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
216
216
  "spec/v1/event-codemap.schema.json": "d05933b2e88103aff51a2f774df97b9bda065e0fc88dbd9b114cc47a32189174",
217
217
  "spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
218
- "spec/v1/gaps.json": "2fc8bb0c3196c62239a10dca000901b77d260b97776893f117eb5d8d144042a9",
218
+ "spec/v1/gaps.json": "07c606b9abbcefbb5fcc18a230765db695ceba9d98f3e7da9dd92b8caebbfd7f",
219
219
  "spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
220
220
  "spec/v1/migrations.json": "4c50760be70cb644d02427bcaf5749606700edf4513f423903cf3b1d12f5c300",
221
221
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
@@ -228,7 +228,7 @@
228
228
  "spec/v2/core/connection-packs.md": "c80f95bd6a03698f35c72cdb2b46bd973f49203bbd786c26760e35fa86e6f23d",
229
229
  "spec/v2/core/conversation.md": "8d6abedf6af408a74577753bfca24719d318e2632e10b45ff5d63bdcf31a8aaa",
230
230
  "spec/v2/core/errors.md": "e48838701b86bbfe0ffee77b18a8ffc4e5f04724b4318507d9771da653f8da0f",
231
- "spec/v2/core/events.md": "370621629c2439747f2e4ddab4780eea4345354f79e5098739d66823873f3105",
231
+ "spec/v2/core/events.md": "c3dcece6f1b1aa43e1e01c5ece23b2df075a505d5f6c2f37de0801aeaa486b62",
232
232
  "spec/v2/core/execution.md": "4218dbb397534940f83ca5d46561f02bf8f3fdf68d2ae7fefe7f2eb2ed771103",
233
233
  "spec/v2/core/form-content-packs.md": "02931227899676fd4581a27017be2a6c4953904717be9b81c29d12f87ab3917d",
234
234
  "spec/v2/core/headers.md": "f69fe81e4a3ad21879bb85948660c16860eb05bf152928c31aa4898f6d7d1887",
@@ -254,7 +254,7 @@
254
254
  "spec/v2/core/workflow-chain-packs.md": "fdce7a8477e269950a2bac05ee6eddbd66eb0b670b464d00054f7196ec05d29c",
255
255
  "spec/v2/corrections.json": "3a8d93e33865b692d26a3b6bfd3819766e206b2e0c2a69669baa4bf833d05a49",
256
256
  "spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
257
- "spec/v2/declaration.json": "773af65df59ce7bc2b3ab17093e23ac8083a8faaeff643302fe30198deae933b",
257
+ "spec/v2/declaration.json": "de8558d76a03d07101c5e26a7311d3e90b449f065c9ba418f4abd9c91864a58b",
258
258
  "spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
259
259
  "spec/v2/eos-override.json": "4c72d2f6a658943b01c1de1643cf294642091933af7832f38ee5495df684cb2d",
260
260
  "spec/v2/errors.json": "1f0e29455edbd22e28f67f221766209c3c55ac4987beb77993b5702dd7cae131",
@@ -281,7 +281,7 @@
281
281
  "spec/v2/ext/sandbox-runtime-notes/README.md": "0d1290fb1e20b2e35c0b15051541ab6546e4e2a6f693b4f912cb3f7d6a65e92c",
282
282
  "spec/v2/ext/webResearch/README.md": "b675e6f6646128d781b661f754c2393eed86e5935d16c3a22df582a5ce025b9b",
283
283
  "spec/v2/facets/a2a.schema.json": "b68bf5e29e455e46c2ad7cfcccf5963730ec2310ff9db81884550d8df30fd6ef",
284
- "spec/v2/facets/agents.schema.json": "62a979d23219ca684bc16f91f81a4c92c2cb56e0f084d2e4f694c03e0c7125df",
284
+ "spec/v2/facets/agents.schema.json": "88fe144572c25e1b09b99fd83e2ad12b2611d4d7366b5d53c3a50d0310faad93",
285
285
  "spec/v2/facets/aiProviders.schema.json": "4eb911c2403b8f618325194855a1326680fbda2549dfbfe86a70dd2743f8fb12",
286
286
  "spec/v2/facets/auditLogIntegrity.schema.json": "9a7f81320c8f940c6f4129bc7dd895a4063231c5458a717a08c2cbf9bb886c46",
287
287
  "spec/v2/facets/auth.schema.json": "7d98bc81a0f7d307460d039eebfebd21388091bfe1d538762583940f94c0bd2a",
@@ -290,6 +290,7 @@
290
290
  "spec/v2/facets/heartbeat.schema.json": "81d7fa1895a8b0241bd07cf00a7e7905bef0fd14eb3427325261c62eee5746a6",
291
291
  "spec/v2/facets/hostEvents.schema.json": "973ff452cb91356b4c625d0b06c8d2ef335e18183de09e1afde8acead22290b3",
292
292
  "spec/v2/facets/interrupt.schema.json": "2110ef32c3f880bf706475ef4cf8ddf97ff06871b8e6003a5edbcf4439f1fb70",
293
+ "spec/v2/facets/limits.schema.json": "218fcbd34dd2cdf7548c29c8c76e1a6aea2b7d7df1ba32fbe201bd40917bc66f",
293
294
  "spec/v2/facets/mcp.schema.json": "7060657dfcff170f079beed160e3db7c1d3cc4410cd3cf64863baf6459f781cd",
294
295
  "spec/v2/facets/nondeterminismPolicy.schema.json": "e9dfaa32e0778a1a837fdad3b8a0673fccda9579ab08c8bfe9777362dd15e989",
295
296
  "spec/v2/facets/oauth.schema.json": "3a0cbe249d3bd5a5982349ae7ba2534776cc958e4a57e0fbeabf2bdab6e99e69",
@@ -313,9 +314,9 @@
313
314
  "spec/v2/path-manifest.json": "7b5679a4dad98c80d855361d88a98c8a4e045af15e310862a05b285b815dc9fc",
314
315
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
315
316
  "spec/v2/profiles.json": "b382b261c7844798575f7c1b37c934f0da19846016a2337d883d4f5ef6e05479",
316
- "spec/v2/release.json": "481c91e5b8cc4965a426e8d946f11c8831192f5c02b3d01295d89a98ff0b0e2a",
317
+ "spec/v2/release.json": "d20dc7e5d37f395199e68fe471244cff4f744790a010a4b0eb3c6c28ba4c26e4",
317
318
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
318
- "spec/v2/surface-baseline.json": "f774b4481aea140f0b9f5b83474ab216763d5fa089e4c7567c830b84a163b4d8"
319
+ "spec/v2/surface-baseline.json": "54bd7632dc314ffbb180b99a35f1367ab9eca60471aae5b98c6a020923c47203"
319
320
  },
320
- "corpusCommit": "b5d6f3aa9cdc0a1f4f3fd705c57842b8c1443ff2"
321
+ "corpusCommit": "ca6b4ad06520858154f4e3dd9ba146496f038e43"
321
322
  }
@@ -221,8 +221,12 @@ export function recordRequirement(
221
221
  */
222
222
  export function readLedgerFile(path: string): readonly LedgerEntry[] {
223
223
  if (!existsSync(path)) return [];
224
- const rank: Record<Disposition, number> = { 'executed-fail': 0, blocked: 1, 'executed-pass': 2, skipped: 3, inapplicable: 4 };
225
- const merged = new Map<string, LedgerEntry>();
224
+ const rank = CERTIFIABILITY_RANK;
225
+ // Pass 1, per (id, scenario file). `<=`, not `<`: a per-`it` FOLD (2.37.0)
226
+ // appends the cumulative row after the leg rows it folded, so on an equal
227
+ // disposition the LAST line is the one carrying the summed `assertionCount`.
228
+ // For genuinely duplicate lines the two are identical and the choice is a no-op.
229
+ const perFile = new Map<string, Map<string, LedgerEntry>>();
226
230
  for (const line of readFileSync(path, 'utf8').split('\n')) {
227
231
  if (line.trim() === '') continue;
228
232
  let e: LedgerEntry;
@@ -232,14 +236,32 @@ export function readLedgerFile(path: string): readonly LedgerEntry[] {
232
236
  continue;
233
237
  }
234
238
  if (typeof e.requirementId !== 'string' || !DISPOSITIONS.includes(e.disposition)) continue;
235
- const prior = merged.get(e.requirementId);
236
- // `<=`, not `<`: a per-`it` FOLD (2.37.0) appends the cumulative row after
237
- // the leg rows it folded, so on an equal disposition the LAST line is the
238
- // one carrying the summed `assertionCount`. For genuinely duplicate lines
239
- // the two are identical and the choice is a no-op.
240
- if (prior === undefined || rank[e.disposition] <= rank[prior.disposition]) merged.set(e.requirementId, e);
239
+ let files = perFile.get(e.requirementId);
240
+ if (files === undefined) perFile.set(e.requirementId, (files = new Map()));
241
+ const file = e.scenarioFile ?? '';
242
+ const prior = files.get(file);
243
+ if (prior === undefined || rank[e.disposition] <= rank[prior.disposition]) files.set(file, e);
241
244
  }
242
- return [...merged.values()].sort((a, b) => a.requirementId.localeCompare(b.requirementId));
245
+ // Pass 2, across files (2.46.11). Two files that witness ONE id run in
246
+ // different workers, so "last line wins" made the row's `assertionCount` and
247
+ // `scenarioFile` whichever worker finished last: the same host read 9 then 5
248
+ // assertions for `prompts.resolution-precedes-composition` with no result
249
+ // change. The least certifiable file still decides the disposition and keeps
250
+ // its detail; counts sum, as the in-worker fold sums them; and a tie names
251
+ // the first file by name, so the row is the same on every run.
252
+ const merged: LedgerEntry[] = [];
253
+ for (const files of perFile.values()) {
254
+ const rows = [...files.values()].sort(
255
+ (a, b) => rank[a.disposition] - rank[b.disposition] || (a.scenarioFile ?? '').localeCompare(b.scenarioFile ?? ''),
256
+ );
257
+ const winner = rows[0]!;
258
+ if (rows.length === 1 || !rows.some((r) => r.assertionCount !== undefined)) {
259
+ merged.push(winner);
260
+ continue;
261
+ }
262
+ merged.push({ ...winner, assertionCount: rows.reduce((n, r) => n + (r.assertionCount ?? 0), 0) });
263
+ }
264
+ return merged.sort((a, b) => a.requirementId.localeCompare(b.requirementId));
243
265
  }
244
266
 
245
267
  /**