diff --git a/Dockerfile b/Dockerfile
index 3b11d2243..840d3983d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,6 +7,14 @@ COPY . .
ARG TARGETOS
ARG TARGETARCH
+ARG VERSION=dev
+ARG BUILDTIME=unknown
+ARG REVISION=unknown
+
+# Make the OCI build metadata available while Angular's static assets are built.
+ENV APP_VERSION=${VERSION} \
+ GIT_COMMIT=${REVISION} \
+ BUILD_DATE=${BUILDTIME}
# Install dependencies
RUN npm install --cpu ${TARGETARCH} --os ${TARGETOS}
@@ -51,4 +59,4 @@ COPY --from=build /workspace/dist/app/browser /usr/share/nginx/html
EXPOSE 80 443
# Command to run NGINX in foreground
-CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/angular.json b/angular.json
index f1ddc95f5..b4225314a 100644
--- a/angular.json
+++ b/angular.json
@@ -73,7 +73,7 @@
{
"type": "initial",
"maximumWarning": "3mb",
- "maximumError": "5mb"
+ "maximumError": "6mb"
},
{
"type": "anyComponentStyle",
diff --git a/docs/local-dev.md b/docs/local-dev.md
index 6fdac153a..76a427de7 100644
--- a/docs/local-dev.md
+++ b/docs/local-dev.md
@@ -78,6 +78,23 @@ ng serve
Open your browser and navigate to `http://localhost:4200`
+### Build information
+
+`ng serve` uses `src/assets/build-info.json`, which contains development
+fallbacks. The repository's `npm run build` and `npm run build-prod` commands
+generate `dist/app/browser/assets/build-info.json` after Angular finishes.
+Provide release metadata through the same variables used by the Docker build:
+
+```bash
+APP_VERSION=4.20.0-beta.23 \
+GIT_COMMIT=c2c017c146fae040caba559333b35536bfbd1189 \
+BUILD_DATE=2026-08-05T15:13:49.915Z \
+npm run build-prod
+```
+
+If variables are omitted, the generated asset uses the package version and
+reports its commit and build date as `unknown`.
+
## Note: Recommended setup using Visual Studio Code Workspaces
@@ -140,4 +157,4 @@ Example VS Code Workspace configuration:
]
}
}
-```
\ No newline at end of file
+```
diff --git a/docs/usage.md b/docs/usage.md
index f7a0dc7c0..7d92911fe 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -2,6 +2,14 @@
The ATT&CK Workbench is a tool intended to allow the ATT&CK community to *explore*, *create*, *annotate* and *share* extensions of ATT&CK.
+## Build Information
+
+The bottom of the primary navigation shows the release versions of the
+Workbench frontend and the connected REST API. Hover over either version to
+see the source commit and build date. If the REST API cannot be reached, its
+version is shown as `unknown` without preventing the rest of the application
+from loading.
+
## Exploring ATT&CK
When first instantiated, the ATT&CK Workbench will not include any data. You can use the application to build a custom dataset, or import data from a data provider such as MITRE using the collections browser.
@@ -18,6 +26,14 @@ Objects may exist in multiple collections simultaneously, and objects can exist
You can read more about the technical specifications for a collection, such as the STIX representation of a collection object, in our [collections](/docs/collections.md) document. MITRE's ATT&CK collections and collection index can be found on our [attack-stix-data GitHub repository](https://github.com/mitre-attack/attack-stix-data).
+#### Releasing a Release-Track Snapshot
+
+The release preview offers minor and major relative tags as well as an exact `MAJOR.MINOR` version. Relative tags are calculated from the tagged snapshot immediately before the selected draft. When releasing an older draft, the exact version must also remain below the next tagged snapshot; the dialog shows these exclusive bounds. Optional release notes are stored on that snapshot and become the `x-mitre-collection` description in exported STIX bundles.
+
+The release-track page can export the latest snapshot or a selected historical snapshot as a STIX 2.0 bundle, a STIX 2.1 bundle, or Workbench JSON. Historical snapshot exports can also copy a concise summary. Tagged snapshots with a bundle cache use that pinned member graph for deterministic member-only exports in either STIX version.
+
+Each cached snapshot card displays server-generated SHA-256 hashes for the exact UTF-8 JSON files produced by its STIX 2.0 and STIX 2.1 bundle downloads. The adjacent copy buttons copy a hash for external file-integrity verification. Snapshot notes are locked while the bundle is cached; delete the cache, edit the notes, and cache the bundle again to generate matching hashes.
+
#### Adding a Collection Index
Collection indexes can be added from the collections page. To add a collection index, specify the URL at which the index is found. The application will then provide a preview of the index for you to review before you save. You can also choose from the provided "recommended collection indexes" to quickly connect your Workbench instance to a data provider without having to find the URL yourself. The ATT&CK Workbench is pre-configured to recommend the MITRE ATT&CK collection index in the "add a collection index" interface.
@@ -226,6 +242,8 @@ When creating and/or editing an object, you can add multiple statements and sele
Any object in the knowledge base, except for marking definitions, can be edited, even those imported from collections. Clicking the "edit" button in the toolbar, or the "edit" link in an object list, will bring you to the edit interface for the object. While editing an object, relationships cannot be viewed or created since they are saved independently of the objects they connect.
+Domain-bearing ATT&CK objects expose a Domain field that reads and writes the STIX `x_mitre_domains` list. This includes techniques, campaigns, mitigations, groups, software, analytics, assets, data components, data sources, detection strategies, matrices, and tactics. When an existing object is revised, its domains are retained unless the editor explicitly changes them.
+
#### Editing Matrices
Matrices share the typical fields on objects, including a description supporting markdown, LinkByIds, and citations. Unlike other object types, their IDs serve as identifier for their domain:
@@ -425,6 +443,7 @@ The source and target objects can be changed after the relationship has been cre
Relationships also have a description to provide additional context or to hold citations of relevant reporting. Like all descriptions, those on relationships support citations, LinkByIds, and markdown formatting. Relationships between sub-techniques and techniques however are purely structural and do not support descriptions.
+Saving a relationship creates a new relationship revision and returns its connected source and target objects to the *work in progress* workflow state through new SDO revisions. Previously published or snapshot-pinned SDO revisions remain unchanged.
### Revoking and Deprecating Objects
@@ -527,4 +546,4 @@ Within the page showing the list of teams, there is an option to create a new te
### Editing a team
-When viewing a team, click the edit icon in the toolbar to edit it. You can edit the name, description, or user list of a team at any time.
\ No newline at end of file
+When viewing a team, click the edit icon in the toolbar to edit it. You can edit the name, description, or user list of a team at any time.
diff --git a/package-lock.json b/package-lock.json
index 6d2ca3410..cbd0160d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,15 +20,13 @@
"@angular/platform-browser": "^19.2.25",
"@angular/platform-browser-dynamic": "^19.2.25",
"@angular/router": "^19.2.25",
- "@mitre-attack/attack-data-model": "^5.0.0-beta.1",
+ "@mitre-attack/attack-data-model": "^5.0.0-beta.3",
"@ng-matero/extensions": "^19.5.1",
"diff-match-patch-ts": "^0.8.0",
- "jdenticon": "^3.2.0",
"lodash": "^4.17.21",
"marked": "^15.0.11",
"moment": "^2.29.4",
"ngx-autosize": "^2.0.4",
- "ngx-jdenticon": "^2.0.0",
"ngx-logger": "^5.0.12",
"ngx-markdown": "^19.1.1",
"rxjs": "^7.8.1",
@@ -4057,9 +4055,9 @@
}
},
"node_modules/@csstools/css-calc": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
- "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz",
+ "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==",
"dev": true,
"funding": [
{
@@ -4081,9 +4079,9 @@
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "4.1.9",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz",
- "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==",
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz",
+ "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==",
"dev": true,
"funding": [
{
@@ -4098,7 +4096,7 @@
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^6.1.0",
- "@csstools/css-calc": "^3.2.1"
+ "@csstools/css-calc": "^3.3.0"
},
"engines": {
"node": ">=20.19.0"
@@ -4132,9 +4130,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz",
- "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz",
+ "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==",
"dev": true,
"funding": [
{
@@ -4638,9 +4636,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
+ "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7038,9 +7036,9 @@
}
},
"node_modules/@parcel/watcher": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
- "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz",
+ "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -7049,7 +7047,7 @@
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">= 10.0.0"
@@ -7059,25 +7057,24 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "@parcel/watcher-android-arm64": "2.5.6",
- "@parcel/watcher-darwin-arm64": "2.5.6",
- "@parcel/watcher-darwin-x64": "2.5.6",
- "@parcel/watcher-freebsd-x64": "2.5.6",
- "@parcel/watcher-linux-arm-glibc": "2.5.6",
- "@parcel/watcher-linux-arm-musl": "2.5.6",
- "@parcel/watcher-linux-arm64-glibc": "2.5.6",
- "@parcel/watcher-linux-arm64-musl": "2.5.6",
- "@parcel/watcher-linux-x64-glibc": "2.5.6",
- "@parcel/watcher-linux-x64-musl": "2.5.6",
- "@parcel/watcher-win32-arm64": "2.5.6",
- "@parcel/watcher-win32-ia32": "2.5.6",
- "@parcel/watcher-win32-x64": "2.5.6"
+ "@parcel/watcher-android-arm64": "2.6.0",
+ "@parcel/watcher-darwin-arm64": "2.6.0",
+ "@parcel/watcher-darwin-x64": "2.6.0",
+ "@parcel/watcher-freebsd-x64": "2.6.0",
+ "@parcel/watcher-linux-arm-glibc": "2.6.0",
+ "@parcel/watcher-linux-arm-musl": "2.6.0",
+ "@parcel/watcher-linux-arm64-glibc": "2.6.0",
+ "@parcel/watcher-linux-arm64-musl": "2.6.0",
+ "@parcel/watcher-linux-x64-glibc": "2.6.0",
+ "@parcel/watcher-linux-x64-musl": "2.6.0",
+ "@parcel/watcher-win32-arm64": "2.6.0",
+ "@parcel/watcher-win32-x64": "2.6.0"
}
},
"node_modules/@parcel/watcher-android-arm64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
- "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz",
+ "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==",
"cpu": [
"arm64"
],
@@ -7096,9 +7093,9 @@
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
- "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz",
+ "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==",
"cpu": [
"arm64"
],
@@ -7117,9 +7114,9 @@
}
},
"node_modules/@parcel/watcher-darwin-x64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
- "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz",
+ "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==",
"cpu": [
"x64"
],
@@ -7138,9 +7135,9 @@
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
- "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz",
+ "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==",
"cpu": [
"x64"
],
@@ -7159,9 +7156,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
- "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz",
+ "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==",
"cpu": [
"arm"
],
@@ -7180,9 +7177,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
- "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz",
+ "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==",
"cpu": [
"arm"
],
@@ -7201,9 +7198,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
- "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz",
+ "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==",
"cpu": [
"arm64"
],
@@ -7222,9 +7219,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
- "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz",
+ "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==",
"cpu": [
"arm64"
],
@@ -7243,9 +7240,9 @@
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
- "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz",
+ "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==",
"cpu": [
"x64"
],
@@ -7264,9 +7261,9 @@
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
- "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz",
+ "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==",
"cpu": [
"x64"
],
@@ -7285,9 +7282,9 @@
}
},
"node_modules/@parcel/watcher-win32-arm64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
- "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz",
+ "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==",
"cpu": [
"arm64"
],
@@ -7305,31 +7302,10 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/watcher-win32-ia32": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
- "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
"node_modules/@parcel/watcher-win32-x64": {
- "version": "2.5.6",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
- "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz",
+ "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==",
"cpu": [
"x64"
],
@@ -7985,9 +7961,9 @@
}
},
"node_modules/@semantic-release/github/node_modules/undici": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
- "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8197,9 +8173,9 @@
}
},
"node_modules/@semantic-release/npm/node_modules/undici": {
- "version": "6.27.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
- "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
+ "version": "6.28.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
+ "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8456,9 +8432,9 @@
}
},
"node_modules/@sinclair/typebox": {
- "version": "0.27.10",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
- "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==",
+ "version": "0.27.12",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz",
+ "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==",
"dev": true,
"license": "MIT"
},
@@ -8509,9 +8485,9 @@
"license": "MIT"
},
"node_modules/@ts-morph/common/node_modules/brace-expansion": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
- "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.3.tgz",
+ "integrity": "sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8610,9 +8586,9 @@
"license": "MIT"
},
"node_modules/@tufjs/models/node_modules/brace-expansion": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
- "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.3.tgz",
+ "integrity": "sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9093,6 +9069,7 @@
"version": "20.19.43",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -9214,17 +9191,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz",
- "integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz",
+ "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.63.0",
- "@typescript-eslint/type-utils": "8.63.0",
- "@typescript-eslint/utils": "8.63.0",
- "@typescript-eslint/visitor-keys": "8.63.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/type-utils": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -9237,22 +9214,22 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.63.0",
+ "@typescript-eslint/parser": "^8.65.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz",
- "integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz",
+ "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.63.0",
- "@typescript-eslint/types": "8.63.0",
- "@typescript-eslint/typescript-estree": "8.63.0",
- "@typescript-eslint/visitor-keys": "8.63.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -9268,14 +9245,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
- "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz",
+ "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.63.0",
- "@typescript-eslint/types": "^8.63.0",
+ "@typescript-eslint/tsconfig-utils": "^8.65.0",
+ "@typescript-eslint/types": "^8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -9290,14 +9267,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
- "integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz",
+ "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.63.0",
- "@typescript-eslint/visitor-keys": "8.63.0"
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9308,9 +9285,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
- "integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz",
+ "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9325,15 +9302,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz",
- "integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz",
+ "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.63.0",
- "@typescript-eslint/typescript-estree": "8.63.0",
- "@typescript-eslint/utils": "8.63.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -9350,9 +9327,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
- "integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz",
+ "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9364,16 +9341,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
- "integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz",
+ "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.63.0",
- "@typescript-eslint/tsconfig-utils": "8.63.0",
- "@typescript-eslint/types": "8.63.0",
- "@typescript-eslint/visitor-keys": "8.63.0",
+ "@typescript-eslint/project-service": "8.65.0",
+ "@typescript-eslint/tsconfig-utils": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -9405,16 +9382,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
- "integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz",
+ "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.63.0",
- "@typescript-eslint/types": "8.63.0",
- "@typescript-eslint/typescript-estree": "8.63.0"
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9429,13 +9406,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz",
- "integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz",
+ "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.63.0",
+ "@typescript-eslint/types": "8.65.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -9878,9 +9855,9 @@
}
},
"node_modules/acorn": {
- "version": "8.17.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
- "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -10519,9 +10496,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.43",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
- "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==",
+ "version": "2.11.6",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.6.tgz",
+ "integrity": "sha512-69D/imtToCsIcAl8WBS2YaRwA4jO/j0HhU+hELqMEu9f54MoUtI6+XH5mrKU8rEFNEk/Ui1I2MK4/JkWacclGw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -10676,9 +10653,9 @@
"license": "MIT"
},
"node_modules/bonjour-service": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.3.tgz",
- "integrity": "sha512-2Kd5UYlFUVgAKMTyuBLl6w49wqfOnbxHqmuH0oCl/n7TfAikR0zoowNOP5BU4dfXmm+Vr9JyEN370auSMx+CNg==",
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.4.tgz",
+ "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10701,16 +10678,16 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
+ "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": "20 || >=22"
}
},
"node_modules/braces": {
@@ -10727,9 +10704,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.28.6",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz",
- "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==",
+ "version": "4.28.7",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz",
+ "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==",
"dev": true,
"funding": [
{
@@ -10747,9 +10724,9 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.10.42",
- "caniuse-lite": "^1.0.30001803",
- "electron-to-chromium": "^1.5.389",
+ "baseline-browser-mapping": "^2.10.44",
+ "caniuse-lite": "^1.0.30001806",
+ "electron-to-chromium": "^1.5.393",
"node-releases": "^2.0.51",
"update-browserslist-db": "^1.2.3"
},
@@ -10881,9 +10858,9 @@
"license": "ISC"
},
"node_modules/cacache/node_modules/tar": {
- "version": "7.5.20",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz",
- "integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==",
+ "version": "7.5.22",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
+ "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -10948,9 +10925,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001805",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz",
- "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==",
+ "version": "1.0.30001806",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
+ "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
"dev": true,
"funding": [
{
@@ -10968,15 +10945,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/canvas-renderer": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.2.1.tgz",
- "integrity": "sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/cfb": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
@@ -13303,9 +13271,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.389",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz",
- "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==",
+ "version": "1.5.397",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.397.tgz",
+ "integrity": "sha512-khGTy9U9x02KEtsKM8vx5A62BsRmcOsIgDpWr1ImE32Ax8GxHGPHZf+Eu9H8zOOyHJnB0jTbseyTHbq2XCT8yw==",
"dev": true,
"license": "ISC"
},
@@ -13386,9 +13354,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.24.2",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz",
- "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==",
+ "version": "5.24.4",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.4.tgz",
+ "integrity": "sha512-GVoi+ICHocoOIU7qVVM48wOJziRsqrsyqlI0Ce0LdowRn6v3bcH2zUa9kp85ncx0nwIb9/HOCOLS3fdThDG/XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -13664,14 +13632,15 @@
}
},
"node_modules/es-toolkit": {
- "version": "1.49.0",
- "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz",
- "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==",
+ "version": "1.50.0",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz",
+ "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==",
"devOptional": true,
"license": "MIT",
"workspaces": [
"docs",
- "benchmarks"
+ "benchmarks",
+ "tests/types"
]
},
"node_modules/esbuild": {
@@ -14385,9 +14354,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
- "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
+ "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
"dev": true,
"funding": [
{
@@ -14636,9 +14605,9 @@
}
},
"node_modules/flatted": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz",
+ "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==",
"dev": true,
"license": "ISC"
},
@@ -14739,9 +14708,9 @@
}
},
"node_modules/fs-extra": {
- "version": "11.3.6",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz",
- "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==",
+ "version": "11.4.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz",
+ "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14996,9 +14965,9 @@
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
- "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.3.tgz",
+ "integrity": "sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15598,9 +15567,9 @@
"license": "MIT"
},
"node_modules/ignore-walk/node_modules/brace-expansion": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
- "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.3.tgz",
+ "integrity": "sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15769,9 +15738,9 @@
}
},
"node_modules/ip-address": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
- "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz",
+ "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -16165,21 +16134,6 @@
"node": ">= 0.6.0"
}
},
- "node_modules/jdenticon": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.3.0.tgz",
- "integrity": "sha512-DhuBRNRIybGPeAjMjdHbkIfiwZCCmf8ggu7C49jhp6aJ7DYsZfudnvnTY5/1vgUhrGA7JaDAx1WevnpjCPvaGg==",
- "license": "MIT",
- "dependencies": {
- "canvas-renderer": "~2.2.0"
- },
- "bin": {
- "jdenticon": "bin/jdenticon.js"
- },
- "engines": {
- "node": ">=6.4.0"
- }
- },
"node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -17466,13 +17420,13 @@
"license": "ISC"
},
"node_modules/minimatch": {
- "version": "10.2.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
- "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^5.0.5"
+ "brace-expansion": "^5.0.8"
},
"engines": {
"node": "18 || 20 || >=22"
@@ -17847,20 +17801,6 @@
"@angular/core": ">12.0.0"
}
},
- "node_modules/ngx-jdenticon": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ngx-jdenticon/-/ngx-jdenticon-2.0.0.tgz",
- "integrity": "sha512-Vs2xiEeYI25tRTAT0Rxcw8di+5vGAFPp+jNRcNnNem93OrgH5VHXh5dx8YKcfo+GeevCWhE5oU418MkgjQi4bw==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.3.0"
- },
- "peerDependencies": {
- "@angular/common": ">=13",
- "@angular/core": ">=13",
- "jdenticon": "^3.2.0"
- }
- },
"node_modules/ngx-logger": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/ngx-logger/-/ngx-logger-5.0.12.tgz",
@@ -17994,9 +17934,9 @@
}
},
"node_modules/node-gyp/node_modules/tar": {
- "version": "7.5.20",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz",
- "integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==",
+ "version": "7.5.22",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
+ "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -20460,9 +20400,9 @@
}
},
"node_modules/p-map": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.5.tgz",
- "integrity": "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.6.tgz",
+ "integrity": "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -20544,9 +20484,9 @@
"license": "BlueOak-1.0.0"
},
"node_modules/package-manager-detector": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.7.0.tgz",
- "integrity": "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==",
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz",
+ "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==",
"license": "MIT",
"optional": true
},
@@ -21256,9 +21196,9 @@
}
},
"node_modules/prettier": {
- "version": "3.9.5",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz",
- "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==",
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
"dev": true,
"license": "MIT",
"bin": {
@@ -21511,9 +21451,9 @@
"license": "MIT"
},
"node_modules/prettier-eslint/node_modules/brace-expansion": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
- "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.3.tgz",
+ "integrity": "sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -22694,9 +22634,9 @@
"license": "MIT"
},
"node_modules/sass": {
- "version": "1.101.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.0.tgz",
- "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==",
+ "version": "1.102.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz",
+ "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -22786,9 +22726,9 @@
}
},
"node_modules/sax": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
- "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
+ "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
"dev": true,
"license": "BlueOak-1.0.0",
"optional": true,
@@ -22876,9 +22816,9 @@
}
},
"node_modules/semantic-release": {
- "version": "25.0.7",
- "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.7.tgz",
- "integrity": "sha512-fFiUD7LNFI0TOGkc49WDHUX4GYKrOMDKct5ReSB1EJCZiPsPdbIPIJGys1xb2ILpK1v9JMsRkjJQgTRpbr7DgQ==",
+ "version": "25.0.8",
+ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.8.tgz",
+ "integrity": "sha512-w/iZ0bur36rKffXZYmIUmy068eoBY3Ij1DCCddx2JwWEM5Tg+eU9ld/E9qSInVvPASyyR2Ln/XGfQ9OZrMlhtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23134,16 +23074,16 @@
}
},
"node_modules/semantic-release/node_modules/yargs": {
- "version": "18.0.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
- "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
+ "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
- "string-width": "^7.2.0",
+ "string-width": "^8.2.1",
"y18n": "^5.0.5",
"yargs-parser": "^22.0.0"
},
@@ -23161,6 +23101,23 @@
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
+ "node_modules/semantic-release/node_modules/yargs/node_modules/string-width": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
+ "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -24616,9 +24573,9 @@
}
},
"node_modules/thingies": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz",
- "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==",
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz",
+ "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -24778,22 +24735,22 @@
}
},
"node_modules/tldts": {
- "version": "7.4.8",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.8.tgz",
- "integrity": "sha512-htwgN/8KRB3z3vnC0BOETVh2m499g5GmyTK9Wq5JBLX3FNz6tSBveAd+fQhzy9hkjif8vy2jwDMR1sGhLtZl2A==",
+ "version": "7.4.9",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.9.tgz",
+ "integrity": "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tldts-core": "^7.4.8"
+ "tldts-core": "^7.4.9"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
- "version": "7.4.8",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.8.tgz",
- "integrity": "sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==",
+ "version": "7.4.9",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.9.tgz",
+ "integrity": "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==",
"dev": true,
"license": "MIT"
},
@@ -25341,16 +25298,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.63.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz",
- "integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz",
+ "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.63.0",
- "@typescript-eslint/parser": "8.63.0",
- "@typescript-eslint/typescript-estree": "8.63.0",
- "@typescript-eslint/utils": "8.63.0"
+ "@typescript-eslint/eslint-plugin": "8.65.0",
+ "@typescript-eslint/parser": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -25395,6 +25352,7 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
@@ -27109,9 +27067,9 @@
"license": "ISC"
},
"node_modules/ws": {
- "version": "8.21.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
- "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
+ "version": "8.21.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
+ "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -27303,9 +27261,9 @@
}
},
"node_modules/yoctocolors": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
- "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz",
+ "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/package.json b/package.json
index b31391462..9e1d0c33a 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,10 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build",
+ "postbuild": "npm run generate:build-info",
"build-prod": "ng build --configuration production",
+ "postbuild-prod": "npm run generate:build-info",
+ "generate:build-info": "node scripts/write-build-info.mjs",
"test": "vitest run",
"test:watch": "vitest watch",
"test:ui": "vitest --ui",
@@ -47,18 +50,16 @@
"@angular/forms": "^19.2.25",
"@angular/material": "^19.2.16",
"@angular/material-moment-adapter": "^19.2.16",
- "@mitre-attack/attack-data-model": "^5.0.0-beta.1",
+ "@mitre-attack/attack-data-model": "^5.0.0-beta.3",
"@angular/platform-browser": "^19.2.25",
"@angular/platform-browser-dynamic": "^19.2.25",
"@angular/router": "^19.2.25",
"@ng-matero/extensions": "^19.5.1",
"diff-match-patch-ts": "^0.8.0",
- "jdenticon": "^3.2.0",
"lodash": "^4.17.21",
"marked": "^15.0.11",
"moment": "^2.29.4",
"ngx-autosize": "^2.0.4",
- "ngx-jdenticon": "^2.0.0",
"ngx-logger": "^5.0.12",
"ngx-markdown": "^19.1.1",
"rxjs": "^7.8.1",
@@ -71,8 +72,6 @@
"@analogjs/vite-plugin-angular": "^1.22.1",
"@analogjs/vitest-angular": "^1.22.1",
"@eslint/js": "^9.39.1",
- "@vitest/coverage-v8": "^3.2.4",
- "@vitest/ui": "^3.2.4",
"@angular-devkit/build-angular": "^19.2.27",
"@angular/cli": "^19.2.27",
"@angular/compiler-cli": "^19.2.25",
@@ -81,6 +80,8 @@
"@types/jasmine": "^6.0.0",
"@types/jasminewd2": "^2.0.10",
"@types/node": "^20.19.43",
+ "@vitest/coverage-v8": "^3.2.4",
+ "@vitest/ui": "^3.2.4",
"angular-eslint": "^19.4.0",
"codelyzer": "^6.0.0",
"commitlint": "^20.5.0",
@@ -91,14 +92,14 @@
"eslint-plugin-unused-imports": "^4.1.4",
"husky": "^9.1.7",
"jsdom": "^27.0.1",
- "sass": "^1.94.2",
"prettier": "^3.8.1",
"prettier-eslint": "^16.3.0",
+ "sass": "^1.94.2",
"semantic-release": "^25.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.8.3",
+ "typescript-eslint": "^8.58.0",
"vite": "^6.4.1",
- "vitest": "^3.2.4",
- "typescript-eslint": "^8.58.0"
+ "vitest": "^3.2.4"
}
}
diff --git a/scripts/write-build-info.mjs b/scripts/write-build-info.mjs
new file mode 100644
index 000000000..cd9ac3b30
--- /dev/null
+++ b/scripts/write-build-info.mjs
@@ -0,0 +1,27 @@
+import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
+const packageInfo = JSON.parse(
+ await readFile(resolve(repositoryRoot, 'package.json'), 'utf8')
+);
+const outputPath = process.env.BUILD_INFO_OUTPUT_PATH
+ ? resolve(process.env.BUILD_INFO_OUTPUT_PATH)
+ : resolve(repositoryRoot, 'dist/app/browser/assets/build-info.json');
+
+function environmentValue(name, fallback) {
+ return process.env[name]?.trim() || fallback;
+}
+
+const buildInfo = {
+ name: packageInfo.name,
+ version: environmentValue('APP_VERSION', packageInfo.version),
+ gitCommit: environmentValue('GIT_COMMIT', 'unknown'),
+ buildDate: environmentValue('BUILD_DATE', 'unknown'),
+};
+
+await mkdir(dirname(outputPath), { recursive: true });
+await writeFile(outputPath, `${JSON.stringify(buildInfo, null, 2)}\n`);
+
+console.log(`Wrote build information to ${outputPath}`);
diff --git a/src/app/app-routing-stix.module.ts b/src/app/app-routing-stix.module.ts
index 370e7a8bd..39c92f9b6 100644
--- a/src/app/app-routing-stix.module.ts
+++ b/src/app/app-routing-stix.module.ts
@@ -5,6 +5,7 @@ import { ReferenceManagerComponent } from './views/reference-manager/reference-m
import { NotesPageComponent } from './views/notes-page/notes-page.component';
import { StixPageComponent } from './views/stix/stix-page/stix-page.component';
import { ContributorsPageComponent } from './views/contributors-page/contributors-page.component';
+import { AllObjectsPageComponent } from './views/stix/all-objects-page/all-objects-page.component';
import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
@@ -21,61 +22,64 @@ const stixRouteData = [
{
attackType: 'matrix',
editable: true,
+ group: 'core',
},
{
attackType: 'tactic',
editable: true,
+ group: 'core',
},
{
attackType: 'technique',
editable: true,
+ group: 'core',
},
// cti
{
attackType: 'group',
editable: true,
- headerSection: 'cti',
+ group: 'cti',
},
{
attackType: 'software',
editable: true,
- headerSection: 'cti',
+ group: 'cti',
},
{
attackType: 'campaign',
editable: true,
- headerSection: 'cti',
+ group: 'cti',
},
// defenses
{
attackType: 'mitigation',
editable: true,
- headerSection: 'defenses',
+ group: 'defenses',
},
{
attackType: 'asset',
editable: true,
- headerSection: 'defenses',
+ group: 'defenses',
},
{
attackType: 'detection-strategy',
editable: true,
- headerSection: 'defenses',
+ group: 'defenses',
},
{
attackType: 'analytic',
editable: true,
- headerSection: 'defenses',
+ group: 'defenses',
},
{
attackType: 'data-component',
editable: true,
- headerSection: 'defenses',
+ group: 'defenses',
},
{
attackType: 'data-source',
editable: true,
- headerSection: 'defenses',
+ group: 'defenses',
deprecated: true,
},
// more
@@ -87,13 +91,24 @@ const stixRouteData = [
];
const stixRoutes: Routes = [];
+stixRoutes.push({
+ path: 'objects',
+ canActivate: [AuthorizationGuard],
+ data: {
+ breadcrumb: 'all objects',
+ title: 'All Objects',
+ roles: viewRoles,
+ },
+ component: AllObjectsPageComponent,
+});
+
stixRouteData.forEach(stixRoute => {
stixRoutes.push({
path: stixRoute.attackType,
canActivateChild: [AuthorizationGuard],
data: {
breadcrumb: AttackTypeToRoute[stixRoute.attackType].replace(/-/g, ' '),
- headerSection: stixRoute.headerSection || undefined,
+ group: stixRoute.group,
deprecated: stixRoute.deprecated ?? false,
},
children: [
@@ -153,7 +168,7 @@ stixRoutes.push({
canActivateChild: [AuthorizationGuard],
data: {
breadcrumb: 'marking definitions',
- headerSection: 'more',
+ group: 'more',
},
children: [
{
@@ -210,7 +225,7 @@ if (environment.integrations.collection_manager.enabled) {
canActivateChild: [AuthorizationGuard],
data: {
breadcrumb: 'collections',
- headerSection: 'more',
+ group: 'more',
},
children: [
{
@@ -307,7 +322,7 @@ stixRoutes.push(
canActivateChild: [AuthorizationGuard],
data: {
breadcrumb: 'reference manager',
- headerSection: 'more',
+ group: 'more',
},
children: [
{
@@ -327,7 +342,7 @@ stixRoutes.push(
canActivateChild: [AuthorizationGuard],
data: {
breadcrumb: 'contributors',
- headerSection: 'more',
+ group: 'more',
},
children: [
{
@@ -346,7 +361,7 @@ stixRoutes.push(
canActivateChild: [AuthorizationGuard],
data: {
breadcrumb: 'notes',
- headerSection: 'more',
+ group: 'more',
},
children: [
{
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index ea3e93aa0..f426ceb08 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -7,15 +7,18 @@ import { DataQualityComponent } from './views/dashboard-page/data-quality/data-q
import { OrgSettingsPageComponent } from './views/dashboard-page/org-settings-page/org-settings-page.component';
import { UserAccountsPageComponent } from './views/dashboard-page/user-accounts-page/user-accounts-page.component';
import { DefaultMarkingDefinitionsComponent } from './views/dashboard-page/default-marking-definitions/default-marking-definitions.component';
+import { ValidationBypassesComponent } from './views/dashboard-page/validation-bypasses/validation-bypasses.component';
import { ProfilePageComponent } from './views/profile-page/profile-page.component';
import { AuthorizationGuard } from './services/helpers/authorization.guard';
import { Role } from './classes/authn/role';
import { TeamsListPageComponent } from './views/dashboard-page/teams/teams-list-page/teams-list-page.component';
import { TeamsViewPageComponent } from './views/dashboard-page/teams/teams-view-page/teams-view-page.component';
+import { ReleaseManagementComponent } from './views/dashboard-page/release-management/release-management.component';
+import { ReleaseTrackPageComponent } from './views/dashboard-page/release-management/release-track-page/release-track-page.component';
const editRoles = [Role.EDITOR, Role.TEAM_LEAD, Role.ADMIN];
-const routes: Routes = [
+export const routes: Routes = [
{
path: '',
data: {
@@ -48,12 +51,44 @@ const routes: Routes = [
children: [
{
path: '',
+ pathMatch: 'full',
+ redirectTo: 'overview',
+ },
+ {
+ path: 'overview',
data: {
- breadcrumb: 'dashboard',
- title: 'Dashboard',
+ breadcrumb: 'overview',
+ title: 'Knowledge Base Overview',
},
component: DashboardPageComponent,
},
+ {
+ path: 'release-management',
+ data: {
+ breadcrumb: 'release management',
+ title: 'Release Management',
+ roles: [Role.ADMIN, Role.TEAM_LEAD],
+ },
+ children: [
+ {
+ path: '',
+ data: {
+ breadcrumb: 'release management',
+ title: 'Release Management',
+ },
+ component: ReleaseManagementComponent,
+ },
+ {
+ path: ':id',
+ data: {
+ breadcrumb: 'view release track',
+ editable: false,
+ title: 'Release Track',
+ },
+ component: ReleaseTrackPageComponent,
+ },
+ ],
+ },
{
path: 'teams',
data: {
@@ -117,6 +152,15 @@ const routes: Routes = [
},
component: DefaultMarkingDefinitionsComponent,
},
+ {
+ path: 'validation-bypasses',
+ data: {
+ breadcrumb: 'validation bypasses',
+ title: 'ADM Validation Bypasses',
+ roles: [Role.ADMIN],
+ },
+ component: ValidationBypassesComponent,
+ },
],
},
{
@@ -128,12 +172,8 @@ const routes: Routes = [
children: [
{
path: '',
- data: {
- breadcrumb: 'documentation',
- markdown: '/assets/docs/README.md',
- title: 'Documentation',
- },
- component: HelpPageComponent,
+ pathMatch: 'full',
+ redirectTo: 'usage',
},
{
path: 'usage',
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 89982da93..d7e1de644 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,17 +1,22 @@
-
+
+
+
+
+
+ (onScrollTop)="scrollToTop()">
close
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index 756b3d9ed..190ebb3a2 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -5,6 +5,7 @@
flex-direction: column;
.app-body {
flex: 1;
+ min-height: 0;
}
.mat-drawer {
overflow-y: hidden;
@@ -18,15 +19,27 @@
height: 100%;
// overflow: auto;
}
+ .app-nav-drawer {
+ width: 286px;
+ border-right: none !important;
+ }
.app-content {
display: flex;
flex-direction: column;
- .router-padding {
- flex: 1;
- box-sizing: border-box;
- padding: 50px 50px;
- overflow: auto;
- }
+ }
+ .page-body {
+ flex: 1;
+ min-height: 0;
+ }
+ .page-content {
+ display: flex;
+ min-height: 0;
+ }
+ .router-padding {
+ flex: 1;
+ box-sizing: border-box;
+ padding: 24px;
+ overflow: auto;
}
mat-drawer {
border-left: none !important;
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 25656badd..fcc4c459e 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,10 +1,8 @@
import {
- AfterViewInit,
Component,
ElementRef,
ViewChild,
ViewEncapsulation,
- OnDestroy,
} from '@angular/core';
import { MatDrawerContainer } from '@angular/material/sidenav';
import { OverlayContainer } from '@angular/cdk/overlay';
@@ -25,20 +23,16 @@ import { AppConfigService } from './services/config/app-config.service';
encapsulation: ViewEncapsulation.None,
standalone: false,
})
-export class AppComponent implements AfterViewInit, OnDestroy {
+export class AppComponent {
// Drawer container to resize when contents change size
@ViewChild(MatDrawerContainer, { static: true })
private container: MatDrawerContainer;
- // Elements for scroll behavior
- @ViewChild('header', { static: false, read: ElementRef })
- private header: ElementRef;
@ViewChild('scrollRef', { static: false, read: ElementRef })
private scrollRef: ElementRef;
public theme;
public alertStatus;
- public hiddenHeaderPX = 0; // number of px of the header which is hidden
public get sidebarOpened() {
return this.sidebarService.opened && this.editorService.sidebarEnabled;
@@ -77,7 +71,7 @@ export class AppComponent implements AfterViewInit, OnDestroy {
const authSubscription = this.authenticationService
.getSession()
.subscribe({
- next: res => {
+ next: () => {
this.checkStatus();
},
complete: () => {
@@ -93,25 +87,6 @@ export class AppComponent implements AfterViewInit, OnDestroy {
initLogger(logger);
}
- ngAfterViewInit(): void {
- // header hiding with scroll
- this.scrollRef.nativeElement.addEventListener(
- 'scroll',
- e => this.adjustHeaderPlacement(),
- true
- );
- // to fix rare cases that the page has resized without scroll events triggering, recompute the offset every 5 seconds
- setInterval(() => this.adjustHeaderPlacement(), 5000);
- }
-
- ngOnDestroy(): void {
- this.scrollRef.nativeElement.removeEventListener(
- 'scroll',
- e => this.adjustHeaderPlacement(),
- true
- );
- }
-
/**
* Check the account status of the logged in user.
* If the user's account status is not active, an alert
@@ -200,18 +175,6 @@ export class AppComponent implements AfterViewInit, OnDestroy {
overlayContainerClasses.add(this.theme);
}
- // adjust the header placement
- private adjustHeaderPlacement(): void {
- const headerHeight = this.header.nativeElement.offsetHeight;
- // constrain amount of hidden to bounds, round up because decimal scroll causes flicker
- this.hiddenHeaderPX = Math.floor(
- Math.min(
- Math.max(0, this.scrollRef.nativeElement.scrollTop / 2),
- headerHeight
- )
- );
- }
-
// scroll to the top of the main content
public scrollToTop(): void {
this.scrollRef.nativeElement.scroll({ top: 0, behavior: 'smooth' });
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index d90fd7223..4ba43f170 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -57,7 +57,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
// other library imports
import { MtxPopoverModule } from '@ng-matero/extensions/popover';
import { AutosizeModule } from 'ngx-autosize';
-import { JDENTICON_CONFIG, NgxJdenticonModule } from 'ngx-jdenticon';
import { MarkdownModule } from 'ngx-markdown';
// custom components
@@ -74,16 +73,22 @@ import { HeaderComponent } from './components/header/header.component';
import { LoadingOverlayComponent } from './components/loading-overlay/loading-overlay.component';
import { MarkdownViewDialogComponent } from './components/markdown-view-dialog/markdown-view-dialog.component';
import { MultipleChoiceDialogComponent } from './components/multiple-choice-dialog/multiple-choice-dialog.component';
+import { NavigationComponent } from './components/navigation/navigation.component';
import { ReferenceEditDialogComponent } from './components/reference-edit-dialog/reference-edit-dialog.component';
-import { HistoryTimelineComponent } from './components/resources-drawer/history-timeline/history-timeline.component';
+import { ReleasePreviewDialogComponent } from './components/release-preview-dialog/release-preview-dialog.component';
+import { SnapshotDescriptionDialogComponent } from './components/snapshot-description-dialog/snapshot-description-dialog.component';
+import { HistoryTimelineComponent } from './components/stix/stix-page-tabs/history-timeline/history-timeline.component';
+import { MembershipSectionComponent } from './components/stix/stix-page-tabs/membership-section/membership-section.component';
import { ReferenceSidebarComponent } from './components/resources-drawer/reference-sidebar/reference-sidebar.component';
import { ResourcesDrawerComponent } from './components/resources-drawer/resources-drawer.component';
import { SearchComponent } from './components/resources-drawer/search/search.component';
import { SaveDialogComponent } from './components/save-dialog/save-dialog.component';
import { SubheadingComponent } from './components/subheading/subheading.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component';
+import { UserAvatarComponent } from './components/user-avatar/user-avatar.component';
import { ValidationResultsComponent } from './components/validation-results/validation-results.component';
import { VersionPopoverComponent } from './components/version-popover/version-popover.component';
+import { WorkflowStatusDialogComponent } from './components/workflow-status-dialog/workflow-status-dialog.component';
// STIX components
import { StixListComponent } from './components/stix/stix-list/stix-list.component';
@@ -145,7 +150,7 @@ import { OrderedListViewComponent } from './components/stix/ordered-list-propert
import { IconViewComponent } from './components/icon-view/icon-view.component';
import { ObjectStatusComponent } from './components/object-status/object-status.component';
import { RecentActivityComponent } from './components/recent-activity/recent-activity.component';
-import { NotesEditorComponent } from './components/resources-drawer/notes-editor/notes-editor.component';
+import { NotesEditorComponent } from './components/stix/stix-page-tabs/notes-editor/notes-editor.component';
import { IdentityPropertyComponent } from './components/stix/identity-property/identity-property.component';
import { CitationEditComponent } from './components/stix/citation-property/citation-edit/citation-edit.component';
@@ -160,9 +165,12 @@ import { DataQualityComponent } from './views/dashboard-page/data-quality/data-q
import { OrgSettingsPageComponent } from './views/dashboard-page/org-settings-page/org-settings-page.component';
import { UserAccountsPageComponent } from './views/dashboard-page/user-accounts-page/user-accounts-page.component';
import { DefaultMarkingDefinitionsComponent } from './views/dashboard-page/default-marking-definitions/default-marking-definitions.component';
+import { ValidationBypassRuleDialogComponent } from './views/dashboard-page/validation-bypasses/validation-bypass-rule-dialog/validation-bypass-rule-dialog.component';
+import { ValidationBypassesComponent } from './views/dashboard-page/validation-bypasses/validation-bypasses.component';
import { ProfilePageComponent } from './views/profile-page/profile-page.component';
import { ReferenceManagerComponent } from './views/reference-manager/reference-manager.component';
+import { AllObjectsPageComponent } from './views/stix/all-objects-page/all-objects-page.component';
import { StixDialogComponent } from './views/stix/stix-dialog/stix-dialog.component';
import { StixPageComponent } from './views/stix/stix-page/stix-page.component';
@@ -229,6 +237,14 @@ import { DictionaryPropertyComponent } from './components/stix/dictionary-proper
import { DictionaryEditComponent } from './components/stix/dictionary-property/dictionary-edit/dictionary-edit.component';
import { DictionaryViewComponent } from './components/stix/dictionary-property/dictionary-view/dictionary-view.component';
import { DictionaryDiffComponent } from './components/stix/dictionary-property/dictionary-diff/dictionary-diff.component';
+import { StixPageTabsComponent } from './components/stix/stix-page-tabs/stix-page-tabs.component';
+import { ReleaseManagementComponent } from './views/dashboard-page/release-management/release-management.component';
+import { ReleaseTrackCardComponent } from './components/release-track-card/release-track-card.component';
+import { ReleaseTrackObjectCardComponent } from './components/release-track-object-card/release-track-object-card.component';
+import { NewTrackDialogComponent } from './components/new-track-dialog/new-track-dialog.component';
+import { ReleaseTrackPageComponent } from './views/dashboard-page/release-management/release-track-page/release-track-page.component';
+import { StatusChipComponent } from './components/status-chip/status-chip.component';
+import { WorkbenchChipComponent } from './components/workbench-chip/workbench-chip.component';
export function initConfig(appConfigService: AppConfigService) {
return () => appConfigService.loadAppConfig();
@@ -238,6 +254,7 @@ export function initConfig(appConfigService: AppConfigService) {
declarations: [
AppComponent,
HeaderComponent,
+ NavigationComponent,
FooterComponent,
LoadingOverlayComponent,
ToolbarComponent,
@@ -248,11 +265,15 @@ export function initConfig(appConfigService: AppConfigService) {
MarkdownViewDialogComponent,
CollectionImportSummaryComponent,
SaveDialogComponent,
+ WorkflowStatusDialogComponent,
AddDialogComponent,
DeleteDialogComponent,
HistoryTimelineComponent,
+ MembershipSectionComponent,
ReferenceSidebarComponent,
ReferenceEditDialogComponent,
+ ReleasePreviewDialogComponent,
+ SnapshotDescriptionDialogComponent,
MultipleChoiceDialogComponent,
ValidationResultsComponent,
AddRelationshipButtonComponent,
@@ -286,12 +307,16 @@ export function initConfig(appConfigService: AppConfigService) {
DatepickerPropertyComponent,
IconViewComponent,
LandingPageComponent,
+ AllObjectsPageComponent,
+ StixPageTabsComponent,
HelpPageComponent,
DashboardPageComponent,
DataQualityComponent,
OrgSettingsPageComponent,
UserAccountsPageComponent,
DefaultMarkingDefinitionsComponent,
+ ValidationBypassesComponent,
+ ValidationBypassRuleDialogComponent,
ProfilePageComponent,
ReferenceManagerComponent,
StixDialogComponent,
@@ -375,11 +400,16 @@ export function initConfig(appConfigService: AppConfigService) {
DictionaryEditComponent,
DictionaryViewComponent,
DictionaryDiffComponent,
+ ReleaseManagementComponent,
+ NewTrackDialogComponent,
+ ReleaseTrackPageComponent,
+ StatusChipComponent,
],
exports: [
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
+ MatSlideToggleModule,
MatIconModule,
MatTableModule,
MatSortModule,
@@ -417,7 +447,6 @@ export function initConfig(appConfigService: AppConfigService) {
disableConsoleLogging: false,
}),
MtxPopoverModule,
- NgxJdenticonModule,
AutosizeModule,
BrowserModule,
AppRoutingModule,
@@ -426,6 +455,7 @@ export function initConfig(appConfigService: AppConfigService) {
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
+ MatSlideToggleModule,
MatIconModule,
MatTableModule,
MatSortModule,
@@ -459,6 +489,10 @@ export function initConfig(appConfigService: AppConfigService) {
ClipboardModule,
OverlayModule,
MatAutocompleteModule,
+ UserAvatarComponent,
+ WorkbenchChipComponent,
+ ReleaseTrackCardComponent,
+ ReleaseTrackObjectCardComponent,
],
providers: [
AppConfigService,
@@ -466,20 +500,6 @@ export function initConfig(appConfigService: AppConfigService) {
const initializerFn = initConfig(inject(AppConfigService));
return initializerFn();
}),
- {
- provide: JDENTICON_CONFIG,
- useValue: {
- lightness: {
- color: [0.35, 0.6],
- grayscale: [0.35, 0.6],
- },
- saturation: {
- color: 0.5,
- grayscale: 0.5,
- },
- backColor: '#0000',
- },
- },
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
diff --git a/src/app/classes/release-tracks/api.ts b/src/app/classes/release-tracks/api.ts
new file mode 100644
index 000000000..af8470ae4
--- /dev/null
+++ b/src/app/classes/release-tracks/api.ts
@@ -0,0 +1,207 @@
+import type { WorkflowStatusType } from 'src/app/utils/types';
+import type { Composition } from './composition';
+import type { ReleaseTrackConfig } from './config';
+import {
+ ReleaseTrackType,
+ type ExportFormatType,
+ type ReleasePreviewFormatType,
+} from './enums';
+import type { SnapshotSchedule } from './release-track';
+
+export type StixObjectRef = string | { id: string; modified?: string };
+
+export interface CreateReleaseTrackPayload {
+ name: string;
+ description?: string;
+ snapshot_description?: string;
+ external_references?: any[];
+ object_marking_refs?: string[];
+ type?: ReleaseTrackType;
+ config?: ReleaseTrackConfig;
+ composition?: Composition;
+ snapshot_schedule?: SnapshotSchedule;
+}
+
+export interface StixBundlePayload {
+ type: 'bundle';
+ id?: string;
+ objects: any[];
+}
+
+export interface UpdateMetadataPayload {
+ name?: string;
+ description?: string;
+ external_references?: any[];
+ object_marking_refs?: string[];
+}
+
+export interface UpdateContentsPayload {
+ x_mitre_contents: string[];
+}
+
+export type ReleasePayload = (
+ | { increment: 'major' | 'minor'; version?: never }
+ | { increment?: never; version: string }
+ | { increment?: undefined; version?: undefined }
+) & { description?: string };
+
+export interface ClonePayload {
+ name?: string;
+}
+
+export interface ReviewPayload {
+ from: WorkflowStatusType;
+ to: WorkflowStatusType;
+ object_refs?: StixObjectRef[];
+}
+
+export interface PromoteQuarantinePayload {
+ object_ref: string;
+ object_modified: string;
+}
+
+export interface ReleaseTrackSnapshotOptions {
+ format?: ExportFormatType;
+ include?: 'members' | 'staged' | 'candidates' | 'quarantine' | 'all';
+ state?: string | string[];
+ stixVersion?: '2.0' | '2.1';
+ includeToc?: boolean;
+}
+
+export interface SnapshotHistoryOptions {
+ tagged?: boolean;
+ limit?: number;
+ offset?: number;
+}
+
+export interface SnapshotGraphStatistics {
+ primary_count: number;
+ secondary_count: number;
+ relationship_count: number;
+ supporting_count: number;
+ link_target_count: number;
+ total_count: number;
+}
+
+export type ReleasePreviewOptions = ReleasePayload & {
+ format?: ReleasePreviewFormatType;
+};
+
+export interface ReleasePreviewSummaryBase {
+ track_id: string;
+ type: ReleaseTrackType;
+ source_snapshot_modified: string;
+ version: string;
+ version_bounds: {
+ lower: { version: string; modified: string } | null;
+ upper: { version: string; modified: string } | null;
+ };
+ releasable: boolean;
+ conflicts: any[];
+}
+
+export interface StandardReleasePreviewSummary extends ReleasePreviewSummaryBase {
+ type: ReleaseTrackType.Standard;
+ before: {
+ members_count: number;
+ staged_count: number;
+ candidates_count: number;
+ };
+ after: {
+ members_count: number;
+ staged_count: number;
+ candidates_count: number;
+ };
+ changes: {
+ promoted_count: number;
+ };
+}
+
+export interface VirtualReleasePreviewSummary extends ReleasePreviewSummaryBase {
+ type: ReleaseTrackType.Virtual;
+ previous_release: {
+ version: string;
+ modified: string;
+ } | null;
+ before: {
+ members_count: number;
+ quarantine_count: number;
+ };
+ after: {
+ members_count: number;
+ quarantine_count: number;
+ };
+ changes: {
+ new_count: number;
+ updated_count: number;
+ removed_count: number;
+ quarantined_count: number;
+ };
+}
+
+export type ReleasePreviewSummary =
+ StandardReleasePreviewSummary | VirtualReleasePreviewSummary;
+
+export interface SnapshotBundleHashes {
+ manifest_id: string;
+ stix_2_0: string;
+ stix_2_1: string;
+}
+
+export interface ReleaseTrackSnapshotHistoryItem {
+ id?: string;
+ modified?: string | Date;
+ version?: string | null;
+ graph_manifest_id?: string;
+ bundle_hashes?: SnapshotBundleHashes;
+ graph_statistics?: SnapshotGraphStatistics;
+ snapshot_description?: string;
+ type?: ReleaseTrackType;
+ name?: string;
+ description?: string;
+ created?: string | Date;
+ tagged_at?: string | Date;
+ snapshot_id?: string | Date;
+ is_latest?: boolean;
+ members_count?: number;
+ staged_count?: number;
+ candidates_count?: number;
+ quarantine_count?: number;
+ added_count?: number;
+ modified_count?: number;
+ promoted_count?: number;
+ members?: any[];
+ staged?: any[];
+ candidates?: any[];
+ contents?: {
+ members?: any[];
+ staged?: any[];
+ candidates?: any[];
+ quarantine?: any[];
+ [key: string]: any;
+ };
+ summary?: {
+ members_count?: number;
+ staged_count?: number;
+ candidates_count?: number;
+ quarantine_count?: number;
+ added_count?: number;
+ modified_count?: number;
+ promoted_count?: number;
+ quarantined_count?: number;
+ [key: string]: any;
+ };
+ statistics?: Record;
+ composition_resolution?: {
+ total_objects?: number;
+ [key: string]: any;
+ };
+ stix?: {
+ id?: string;
+ modified?: string | Date;
+ x_mitre_version?: string | null;
+ x_mitre_contents?: any[];
+ [key: string]: any;
+ };
+ [key: string]: any;
+}
diff --git a/src/app/classes/release-tracks/component-track.ts b/src/app/classes/release-tracks/component-track.ts
new file mode 100644
index 000000000..28d798b18
--- /dev/null
+++ b/src/app/classes/release-tracks/component-track.ts
@@ -0,0 +1,34 @@
+// -----------------------------------------------------------------------------
+// Component Tracks
+//
+// Release track (standard or virtual) referenced by a virtual release track
+// -----------------------------------------------------------------------------
+
+import { ResolutionStrategyType } from './enums';
+
+export interface ComponentTrackFilters {
+ object_types?: string[];
+ domains?: string[];
+}
+
+export interface ComponentTrack {
+ track_id: string;
+ resolution_strategy: ResolutionStrategyType;
+ priority: number;
+ version?: string | null;
+ snapshot?: Date;
+ filters?: ComponentTrackFilters;
+}
+
+export interface ComponentSnapshotResolution {
+ track_id: string;
+ track_name: string;
+ track_type: string;
+ resolved_snapshot_id: Date;
+ resolved_version?: string | null;
+ strategy_used: string;
+ filters_applied?: ComponentTrackFilters;
+ total_objects_in_source: number;
+ objects_after_filter: number;
+ objects_contributed: number;
+}
diff --git a/src/app/classes/release-tracks/composition.ts b/src/app/classes/release-tracks/composition.ts
new file mode 100644
index 000000000..c24592547
--- /dev/null
+++ b/src/app/classes/release-tracks/composition.ts
@@ -0,0 +1,35 @@
+// -----------------------------------------------------------------------------
+// Composition
+//
+// Rules and configuration that defines how a virtual release track
+// aggregates content from component tracks
+// -----------------------------------------------------------------------------
+
+import { DeduplicationStrategyType, SnapshotTierType } from './enums';
+import { ComponentTrack, ComponentSnapshotResolution } from './component-track';
+import { WorkflowStatusType } from 'src/app/utils/types';
+
+export interface Composition {
+ component_tracks?: ComponentTrack[];
+ deduplication?: {
+ strategy?: DeduplicationStrategyType;
+ // When resolving conflicts between versions, which snapshot tier should be
+ // preferred (e.g., prefer from 'staged' over 'candidate' or vice-versa).
+ tier_resolution?: SnapshotTierType;
+ // When resolving conflicts, prefer the object with the highest workflow
+ // status (e.g., 'reviewed' over 'awaiting-review').
+ status_resolution?: WorkflowStatusType;
+ };
+}
+
+export interface CompositionResolution {
+ resolved_at?: Date;
+ component_snapshots?: ComponentSnapshotResolution[];
+ deduplication?: {
+ total_objects_before?: number;
+ total_objects_after?: number;
+ duplicates_found?: number;
+ conflicts_resolved?: any[];
+ };
+ summary?: any;
+}
diff --git a/src/app/classes/release-tracks/config.ts b/src/app/classes/release-tracks/config.ts
new file mode 100644
index 000000000..4d8c6bbb4
--- /dev/null
+++ b/src/app/classes/release-tracks/config.ts
@@ -0,0 +1,31 @@
+// -----------------------------------------------------------------------------
+// Release Track Configuration
+// -----------------------------------------------------------------------------
+
+import { WorkflowStatusType } from 'src/app/utils/types';
+import type {
+ ConflictPolicyType,
+ MemberSyncBehaviorType,
+ MemberSyncPolicyType,
+ MemberSyncStrategyType,
+} from './enums';
+
+export interface ReleaseTrackConfig {
+ candidacy_threshold?: WorkflowStatusType;
+ auto_promote?: boolean;
+ include_secondary_objects?: {
+ enabled?: boolean;
+ status_threshold?: WorkflowStatusType;
+ };
+ promotion_conflicts?: {
+ candidates_to_staged?: ConflictPolicyType;
+ staged_to_members?: ConflictPolicyType;
+ };
+ member_sync?: {
+ strategy?: MemberSyncStrategyType;
+ supplant?: {
+ behavior?: MemberSyncBehaviorType;
+ status_policy?: MemberSyncPolicyType;
+ };
+ };
+}
diff --git a/src/app/classes/release-tracks/enums.ts b/src/app/classes/release-tracks/enums.ts
new file mode 100644
index 000000000..694c488e7
--- /dev/null
+++ b/src/app/classes/release-tracks/enums.ts
@@ -0,0 +1,165 @@
+import { WorkflowStatus, WorkflowStatusType } from 'src/app/utils/types';
+
+type EnumValue> = T[keyof T];
+
+// -----------------------------------------------------------------------------
+// Release Track Type
+// -----------------------------------------------------------------------------
+
+export enum ReleaseTrackType {
+ Standard = 'standard',
+ Virtual = 'virtual',
+}
+
+export const RELEASE_TRACK_TYPE_OPTIONS: ReleaseTrackType[] = Object.values(
+ ReleaseTrackType
+) as ReleaseTrackType[];
+
+// -----------------------------------------------------------------------------
+// Conflict Resolution
+// -----------------------------------------------------------------------------
+
+export enum ConflictPolicy {
+ AlwaysOverwrite = 'always_overwrite', // replace with incoming entry
+ AlwaysReject = 'always_reject', // always reject incoming
+ PreferLatest = 'prefer_latest', // keep whichever has newer object_modified
+ Abort = 'abort', // throw error on any conflict
+}
+
+export type ConflictPolicyType = EnumValue;
+
+export const CONFLICT_POLICY_OPTIONS: ConflictPolicyType[] = Object.values(
+ ConflictPolicy
+) as ConflictPolicyType[];
+
+// -----------------------------------------------------------------------------
+// Deduplication Strategy
+// -----------------------------------------------------------------------------
+
+export enum DeduplicationStrategy {
+ PrioritizeLatestObject = 'prioritize_latest_object', // keep version with newest object_modified
+ PrioritizeLatestSnapshot = 'prioritize_latest_snapshot', // keep version from most recently modified snapshot
+ PrioritizeHigherPriority = 'prioritize_higher_priority', // keep version from the higher-priority component (lower number)
+ Quarantine = 'quarantine', // send all conflicting versions to quarantine for manual review
+}
+
+export type DeduplicationStrategyType = EnumValue;
+
+export const DEDUPLICATION_STRATEGY_OPTIONS: DeduplicationStrategyType[] =
+ Object.values(DeduplicationStrategy) as DeduplicationStrategyType[];
+
+// -----------------------------------------------------------------------------
+// Export Format
+// -----------------------------------------------------------------------------
+
+export enum ExportFormat {
+ Bundle = 'bundle',
+ Workbench = 'workbench',
+ FileSystemStore = 'filesystemstore',
+}
+
+export type ExportFormatType = EnumValue;
+
+export const EXPORT_FORMAT_OPTIONS: ExportFormatType[] = Object.values(
+ ExportFormat
+) as ExportFormatType[];
+
+export enum ReleasePreviewFormat {
+ Summary = 'summary',
+ Bundle = 'bundle',
+ Workbench = 'workbench',
+ FileSystemStore = 'filesystemstore',
+}
+
+export type ReleasePreviewFormatType = EnumValue;
+
+// -----------------------------------------------------------------------------
+// Release Track Snapshot Tiers
+// -----------------------------------------------------------------------------
+
+export enum SnapshotTier {
+ Member = 'released',
+ Staged = 'staged',
+ Candidate = 'candidate',
+ All = 'all',
+}
+
+export type SnapshotTierType = EnumValue;
+
+export const SNAPSHOT_TIER_OPTIONS: SnapshotTierType[] = Object.values(
+ SnapshotTier
+) as SnapshotTierType[];
+
+// -----------------------------------------------------------------------------
+// Candidacy Thresholds
+// -----------------------------------------------------------------------------
+
+export const CANDIDACY_THRESHOLD_OPTIONS: WorkflowStatusType[] = Object.values(
+ WorkflowStatus
+) as WorkflowStatusType[];
+
+// -----------------------------------------------------------------------------
+// Resolution Strategy
+// -----------------------------------------------------------------------------
+
+export enum ResolutionStrategy {
+ LatestTagged = 'latest_tagged',
+ SpecificVersion = 'specific_version',
+ SpecificSnapshot = 'specific_snapshot',
+}
+
+export type ResolutionStrategyType = EnumValue;
+
+export const RESOLUTION_STRATEGY_OPTIONS: ResolutionStrategyType[] =
+ Object.values(ResolutionStrategy) as ResolutionStrategyType[];
+
+// -----------------------------------------------------------------------------
+// Snapshot Schedule Modes
+// -----------------------------------------------------------------------------
+
+export enum SnapshotScheduleMode {
+ Manual = 'manual',
+ Cron = 'cron',
+ Dates = 'dates',
+}
+
+export type SnapshotScheduleModeType = EnumValue;
+
+export const SNAPSHOT_MODE_OPTIONS: SnapshotScheduleModeType[] = Object.values(
+ SnapshotScheduleMode
+) as SnapshotScheduleModeType[];
+
+// -----------------------------------------------------------------------------
+// Member Sync
+// -----------------------------------------------------------------------------
+
+export enum MemberSyncStrategy {
+ TrackLatest = 'track_latest',
+ Manual = 'manual',
+}
+
+export type MemberSyncStrategyType = EnumValue;
+
+export const MEMBER_SYNC_STRATEGY_OPTIONS: MemberSyncStrategyType[] =
+ Object.values(MemberSyncStrategy) as MemberSyncStrategyType[];
+
+export enum MemberSyncBehavior {
+ Replace = 'replace',
+ Queue = 'queue',
+ Ignore = 'ignore',
+}
+
+export type MemberSyncBehaviorType = EnumValue;
+
+export const MEMBER_SYNC_BEHAVIOR_OPTIONS: MemberSyncBehaviorType[] =
+ Object.values(MemberSyncBehavior) as MemberSyncBehaviorType[];
+
+export enum MemberSyncPolicy {
+ Reset = 'reset',
+ Preserve = 'preserve',
+}
+
+export type MemberSyncPolicyType = EnumValue;
+
+export const MEMBER_SYNC_STATUS_POLICY_OPTIONS: MemberSyncPolicyType[] =
+ Object.values(MemberSyncPolicy) as MemberSyncPolicyType[];
diff --git a/src/app/classes/release-tracks/history.ts b/src/app/classes/release-tracks/history.ts
new file mode 100644
index 000000000..0eb628809
--- /dev/null
+++ b/src/app/classes/release-tracks/history.ts
@@ -0,0 +1,13 @@
+export interface VersionHistoryEntry {
+ version: string;
+ tagged_at: Date;
+ tagged_by: string;
+ snapshot_id: Date;
+ summary?: {
+ members_count?: number;
+ promoted_count?: number;
+ staged_count?: number;
+ candidate_count?: number;
+ };
+ component_versions?: any; // virtual tracks only
+}
diff --git a/src/app/classes/release-tracks/index.ts b/src/app/classes/release-tracks/index.ts
new file mode 100644
index 000000000..cc65c34d4
--- /dev/null
+++ b/src/app/classes/release-tracks/index.ts
@@ -0,0 +1,9 @@
+export * from './api';
+export * from './component-track';
+export * from './composition';
+export * from './config';
+export * from './enums';
+export * from './history';
+export * from './release-track';
+export * from './snapshot';
+export * from './tiers';
diff --git a/src/app/classes/release-tracks/release-track.ts b/src/app/classes/release-tracks/release-track.ts
new file mode 100644
index 000000000..0ee7b53ae
--- /dev/null
+++ b/src/app/classes/release-tracks/release-track.ts
@@ -0,0 +1,24 @@
+import { ReleaseTrackType, SnapshotScheduleModeType } from './enums';
+
+export interface ReleaseTrack {
+ track_id: string;
+ type: ReleaseTrackType;
+ name: string;
+ description?: string;
+ created_at: Date;
+ updated_at: Date;
+
+ latest_snapshot_modified?: Date | null;
+ latest_tagged_version?: string | null;
+ snapshot_count?: number;
+ tagged_release_count?: number;
+
+ // virtual tracks only
+ snapshot_schedule?: SnapshotSchedule;
+}
+
+export interface SnapshotSchedule {
+ mode?: SnapshotScheduleModeType;
+ cron?: string | null;
+ dates?: Date[] | undefined;
+}
diff --git a/src/app/classes/release-tracks/snapshot.spec.ts b/src/app/classes/release-tracks/snapshot.spec.ts
new file mode 100644
index 000000000..ffa548722
--- /dev/null
+++ b/src/app/classes/release-tracks/snapshot.spec.ts
@@ -0,0 +1,146 @@
+import { ReleaseTrackSnapshot } from './snapshot';
+
+describe('ReleaseTrackSnapshot', () => {
+ it('should preserve snapshot-local notes separately from track metadata', () => {
+ const snapshot = new ReleaseTrackSnapshot({
+ description: 'Long-lived track purpose',
+ snapshot_description: 'Context for this release',
+ });
+
+ expect(snapshot.description).toBe('Long-lived track purpose');
+ expect(snapshot.snapshot_description).toBe('Context for this release');
+ expect(snapshot.serialize()).toEqual(
+ expect.objectContaining({
+ description: 'Long-lived track purpose',
+ snapshot_description: 'Context for this release',
+ })
+ );
+ });
+
+ it('should preserve candidate and staged display fields', () => {
+ const snapshot = new ReleaseTrackSnapshot({
+ candidates: [
+ {
+ object_ref: 'attack-pattern--candidate',
+ object_modified: '2026-01-01T00:00:00.000Z',
+ object_status: 'work-in-progress',
+ attack_id: 'T1234',
+ name: 'Candidate Technique',
+ description: 'Candidate description',
+ modified_by_user: {
+ id: 'user-account--candidate',
+ username: 'candidateuser',
+ name: 'Candidate Reviewer',
+ },
+ },
+ ],
+ staged: [
+ {
+ object_ref: 'attack-pattern--staged',
+ object_modified: '2026-01-02T00:00:00.000Z',
+ object_status: 'reviewed',
+ attack_id: 'T5678',
+ name: 'Staged Technique',
+ description: 'Staged description',
+ modified_by_user: {
+ id: 'user-account--staged',
+ username: 'stageduser',
+ name: 'Staged Reviewer',
+ },
+ },
+ ],
+ members: [
+ {
+ object_ref: 'attack-pattern--member',
+ object_modified: '2026-01-03T00:00:00.000Z',
+ attack_id: 'T9999',
+ name: 'Member Technique',
+ },
+ ],
+ });
+
+ expect(snapshot.candidates?.[0]).toEqual(
+ expect.objectContaining({
+ attack_id: 'T1234',
+ name: 'Candidate Technique',
+ description: 'Candidate description',
+ modified_by_user: expect.objectContaining({
+ name: 'Candidate Reviewer',
+ }),
+ })
+ );
+ expect(snapshot.staged?.[0]).toEqual(
+ expect.objectContaining({
+ attack_id: 'T5678',
+ name: 'Staged Technique',
+ description: 'Staged description',
+ modified_by_user: expect.objectContaining({
+ name: 'Staged Reviewer',
+ }),
+ })
+ );
+ });
+
+ it('should preserve latest selectors for candidate and staged revisions', () => {
+ const snapshot = new ReleaseTrackSnapshot({
+ candidates: [
+ {
+ object_ref: 'attack-pattern--candidate',
+ object_modified: 'latest',
+ object_status: 'work-in-progress',
+ },
+ ],
+ staged: [
+ {
+ object_ref: 'attack-pattern--staged',
+ object_modified: 'latest',
+ object_status: 'reviewed',
+ },
+ ],
+ });
+
+ expect(snapshot.candidates?.[0].object_modified).toBe('latest');
+ expect(snapshot.staged?.[0].object_modified).toBe('latest');
+ expect(snapshot.serialize()).toEqual(
+ expect.objectContaining({
+ candidates: [
+ expect.objectContaining({
+ object_modified: 'latest',
+ }),
+ ],
+ staged: [
+ expect.objectContaining({
+ object_modified: 'latest',
+ }),
+ ],
+ })
+ );
+ });
+
+ it('should deserialize and serialize exact workflow revision timestamps', () => {
+ const modified = '2026-01-04T00:00:00.000Z';
+ const snapshot = new ReleaseTrackSnapshot({
+ candidates: [
+ {
+ object_ref: 'attack-pattern--candidate',
+ object_modified: modified,
+ object_status: 'work-in-progress',
+ },
+ ],
+ staged: [
+ {
+ object_ref: 'attack-pattern--staged',
+ object_modified: modified,
+ object_status: 'reviewed',
+ },
+ ],
+ });
+
+ expect(snapshot.candidates?.[0].object_modified).toEqual(
+ new Date(modified)
+ );
+ expect(snapshot.staged?.[0].object_modified).toEqual(new Date(modified));
+ expect(snapshot.serialize().candidates[0].object_modified).toBe(modified);
+ expect(snapshot.serialize().staged[0].object_modified).toBe(modified);
+ });
+});
diff --git a/src/app/classes/release-tracks/snapshot.ts b/src/app/classes/release-tracks/snapshot.ts
new file mode 100644
index 000000000..8af8a6bb3
--- /dev/null
+++ b/src/app/classes/release-tracks/snapshot.ts
@@ -0,0 +1,317 @@
+import { Composition, CompositionResolution } from './composition';
+import { ReleaseTrackConfig } from './config';
+import { ReleaseTrackType } from './enums';
+import { VersionHistoryEntry } from './history';
+import { SnapshotSchedule } from './release-track';
+import {
+ CandidateEntry,
+ MemberEntry,
+ QuarantineEntry,
+ StagedEntry,
+ WorkflowRevisionSelector,
+} from './tiers';
+
+export class ReleaseTrackSnapshot {
+ public id = '';
+ public type: ReleaseTrackType = ReleaseTrackType.Standard;
+ public modified: Date = new Date();
+ public version?: string | null;
+ public name = '';
+ public description?: string;
+ public snapshot_description?: string;
+ public created: Date = new Date();
+ public created_by_ref?: string;
+ public object_marking_refs?: string[];
+
+ public config: ReleaseTrackConfig = {} as ReleaseTrackConfig;
+ public version_history: VersionHistoryEntry[] = [];
+ public summary?: Record;
+
+ // standard track tiers
+ public members: MemberEntry[] = [];
+ public staged?: StagedEntry[];
+ public candidates?: CandidateEntry[];
+
+ // virtual track tiers
+ public quarantine?: QuarantineEntry[];
+
+ // virtual track composition
+ public composition?: Composition;
+ public composition_resolution?: CompositionResolution;
+ public snapshot_schedule?: SnapshotSchedule;
+
+ constructor(raw?: any) {
+ if (raw) this.deserialize(raw);
+ }
+
+ public get isVirtual(): boolean {
+ return this.type === ReleaseTrackType.Virtual;
+ }
+
+ public get isStandard(): boolean {
+ return this.type === ReleaseTrackType.Standard;
+ }
+
+ public get memberCount(): number {
+ return this.members ? this.members.length : 0;
+ }
+
+ public get stagedCount(): number {
+ return this.staged ? this.staged.length : 0;
+ }
+
+ public get candidateCount(): number {
+ return this.candidates ? this.candidates.length : 0;
+ }
+
+ public get quarantineCount(): number {
+ return this.quarantine ? this.quarantine.length : 0;
+ }
+
+ public get isTagged(): boolean {
+ return !!this.version;
+ }
+
+ public get latestTaggedVersion(): string | null {
+ if (!this.version_history || this.version_history.length === 0) return null;
+ const sorted = [...this.version_history].sort((a, b) => {
+ const ta = a.tagged_at ? new Date(a.tagged_at).getTime() : 0;
+ const tb = b.tagged_at ? new Date(b.tagged_at).getTime() : 0;
+ return tb - ta;
+ });
+ return sorted[0].version || null;
+ }
+
+ // Populate release track snapshot fields from a raw object
+ public deserialize(raw: any) {
+ if (!raw) return;
+
+ if ('id' in raw) this.id = raw.id;
+ if ('type' in raw) this.type = raw.type;
+ if ('modified' in raw) this.modified = new Date(raw.modified);
+ if ('version' in raw) this.version = raw.version;
+ if ('name' in raw) this.name = raw.name;
+ if ('description' in raw) this.description = raw.description;
+ if ('snapshot_description' in raw)
+ this.snapshot_description = raw.snapshot_description;
+ if ('created' in raw) this.created = new Date(raw.created);
+ if ('created_by_ref' in raw) this.created_by_ref = raw.created_by_ref;
+ if ('object_marking_refs' in raw && Array.isArray(raw.object_marking_refs))
+ this.object_marking_refs = raw.object_marking_refs.slice();
+
+ if ('config' in raw) this.config = raw.config;
+ if ('summary' in raw) this.summary = raw.summary;
+
+ if ('version_history' in raw && Array.isArray(raw.version_history)) {
+ this.version_history = raw.version_history.map((v: any) => {
+ const entry: VersionHistoryEntry = { ...v } as any;
+ if (v.tagged_at) entry.tagged_at = new Date(v.tagged_at);
+ if (v.snapshot_id) entry.snapshot_id = new Date(v.snapshot_id);
+ return entry;
+ });
+ }
+
+ if ('members' in raw && Array.isArray(raw.members)) {
+ this.members = raw.members.map((m: any) => ({
+ ...m,
+ object_ref: m.object_ref,
+ object_modified: m.object_modified
+ ? new Date(m.object_modified)
+ : undefined,
+ }));
+ }
+
+ if ('staged' in raw && Array.isArray(raw.staged)) {
+ this.staged = raw.staged.map((s: any) => ({
+ ...s,
+ object_ref: s.object_ref,
+ object_modified: this.deserializeWorkflowRevision(s.object_modified),
+ object_status: s.object_status,
+ object_staged_at: s.object_staged_at
+ ? new Date(s.object_staged_at)
+ : undefined,
+ object_staged_by: s.object_staged_by,
+ }));
+ }
+
+ if ('candidates' in raw && Array.isArray(raw.candidates)) {
+ this.candidates = raw.candidates.map((c: any) => ({
+ ...c,
+ object_ref: c.object_ref,
+ object_modified: this.deserializeWorkflowRevision(c.object_modified),
+ object_status: c.object_status,
+ object_added_at: c.object_added_at
+ ? new Date(c.object_added_at)
+ : undefined,
+ object_added_by: c.object_added_by,
+ }));
+ }
+
+ if ('quarantine' in raw && Array.isArray(raw.quarantine)) {
+ this.quarantine = raw.quarantine.map((q: any) => ({
+ ...q,
+ object_ref: q.object_ref,
+ object_modified: q.object_modified
+ ? new Date(q.object_modified)
+ : undefined,
+ source_track_id: q.source_track_id,
+ source_track_name: q.source_track_name,
+ source_snapshot_version: q.source_snapshot_version,
+ conflict_reason: q.conflict_reason,
+ }));
+ }
+
+ if ('composition' in raw) this.composition = raw.composition;
+
+ if ('snapshot_schedule' in raw)
+ this.snapshot_schedule = raw.snapshot_schedule;
+
+ if ('composition_resolution' in raw && raw.composition_resolution) {
+ const cr = raw.composition_resolution;
+ const resolved: CompositionResolution = { ...cr } as any;
+ if (cr.resolved_at) resolved.resolved_at = new Date(cr.resolved_at);
+ if (cr.component_snapshots && Array.isArray(cr.component_snapshots)) {
+ resolved.component_snapshots = cr.component_snapshots.map(
+ (cs: any) => ({
+ ...cs,
+ resolved_snapshot_id: cs.resolved_snapshot_id
+ ? new Date(cs.resolved_snapshot_id)
+ : undefined,
+ })
+ );
+ }
+ this.composition_resolution = resolved;
+ }
+ }
+
+ // Generate object representation of the release track snapshot
+ public serialize(): any {
+ return {
+ id: this.id,
+ type: this.type,
+ modified: this.modified ? this.modified.toISOString() : undefined,
+ version: this.version,
+ name: this.name,
+ description: this.description,
+ snapshot_description: this.snapshot_description,
+ created: this.created ? this.created.toISOString() : undefined,
+ created_by_ref: this.created_by_ref,
+ object_marking_refs: this.object_marking_refs,
+ config: this.config,
+ summary: this.summary,
+ version_history: this.version_history?.map(v => ({
+ ...v,
+ tagged_at: v.tagged_at
+ ? (v.tagged_at as any).toISOString()
+ : v.tagged_at,
+ snapshot_id: v.snapshot_id
+ ? (v.snapshot_id as any).toISOString()
+ : v.snapshot_id,
+ })),
+ members: this.members?.map(m => ({
+ ...m,
+ object_modified: m.object_modified
+ ? (m.object_modified as any).toISOString()
+ : m.object_modified,
+ })),
+ staged: this.staged?.map(s => ({
+ ...s,
+ object_modified: this.serializeWorkflowRevision(s.object_modified),
+ object_staged_at: s.object_staged_at
+ ? (s.object_staged_at as any).toISOString()
+ : s.object_staged_at,
+ })),
+ candidates: this.candidates?.map(c => ({
+ ...c,
+ object_modified: this.serializeWorkflowRevision(c.object_modified),
+ object_added_at: c.object_added_at
+ ? (c.object_added_at as any).toISOString()
+ : c.object_added_at,
+ })),
+ quarantine: this.quarantine?.map(q => ({
+ ...q,
+ object_modified: q.object_modified
+ ? (q.object_modified as any).toISOString()
+ : q.object_modified,
+ })),
+ composition: this.composition,
+ composition_resolution: this.composition_resolution,
+ snapshot_schedule: this.snapshot_schedule,
+ };
+ }
+
+ // Check if an object with the given STIX ID exists in members
+ public hasMember(objectRef: string): boolean {
+ return !!this.members.find(m => m.object_ref === objectRef);
+ }
+
+ // Check if an object with the given STIX ID exists in candidates
+ public hasCandidate(objectRef: string): boolean {
+ return !!this.candidates?.find(c => c.object_ref === objectRef);
+ }
+
+ // Check if an object with the given STIX ID exists in staged
+ public hasStaged(objectRef: string): boolean {
+ return !!this.staged?.find(c => c.object_ref === objectRef);
+ }
+
+ // Get the member entry for the given STIX ID
+ public findMember(objectRef: string): MemberEntry | undefined {
+ return this.members.find(m => m.object_ref === objectRef);
+ }
+
+ private deserializeWorkflowRevision(
+ value: string | Date | undefined
+ ): WorkflowRevisionSelector | undefined {
+ if (!value) return undefined;
+ return value === 'latest' ? 'latest' : new Date(value);
+ }
+
+ private serializeWorkflowRevision(
+ value: WorkflowRevisionSelector | undefined
+ ): string | undefined {
+ if (value === undefined) return undefined;
+ if (value === 'latest') return value;
+ return value.toISOString();
+ }
+
+ // Get the candidate entry for the given STIX ID
+ public findCandidate(objectRef: string): CandidateEntry | undefined {
+ return this.candidates?.find(c => c.object_ref === objectRef);
+ }
+
+ // Get the staged entry for the given STIX ID
+ public findStaged(objectRef: string): StagedEntry | undefined {
+ return this.staged?.find(c => c.object_ref === objectRef);
+ }
+
+ // Add candidate entry
+ public addCandidate(entry: CandidateEntry): void {
+ if (!this.candidates) this.candidates = [];
+ if (!this.hasCandidate(entry.object_ref)) this.candidates.push(entry);
+ }
+
+ // Remove candidate entry
+ public removeCandidate(objectRef: string): void {
+ if (!this.candidates) return;
+ this.candidates = this.candidates.filter(c => c.object_ref !== objectRef);
+ }
+
+ // Get a summary of the snapshot object
+ public toSummary(): any {
+ return {
+ id: this.id,
+ name: this.name,
+ type: this.type,
+ version: this.version,
+ modified: this.modified,
+ snapshot_description: this.snapshot_description,
+ counts: {
+ members: this.memberCount,
+ staged: this.stagedCount,
+ candidates: this.candidateCount,
+ quarantine: this.quarantineCount,
+ },
+ };
+ }
+}
diff --git a/src/app/classes/release-tracks/tiers.ts b/src/app/classes/release-tracks/tiers.ts
new file mode 100644
index 000000000..bae5f0ad9
--- /dev/null
+++ b/src/app/classes/release-tracks/tiers.ts
@@ -0,0 +1,51 @@
+import { WorkflowStatusType } from 'src/app/utils/types';
+import { SnapshotTier } from './enums';
+
+export type ReleaseTrackObjectTier =
+ SnapshotTier.Candidate | SnapshotTier.Staged;
+
+export type WorkflowRevisionSelector = Date | 'latest';
+
+export interface TierEntryModifiedByUser {
+ id?: string;
+ username?: string;
+ displayName?: string;
+ name?: string;
+}
+
+export interface TierEntryDisplayFields {
+ attack_id?: string;
+ name?: string;
+ description?: string;
+ modified_by_user?: TierEntryModifiedByUser;
+}
+
+export interface MemberEntry {
+ object_ref: string;
+ object_modified: Date;
+}
+
+export interface StagedEntry extends TierEntryDisplayFields {
+ object_ref: string;
+ object_modified: WorkflowRevisionSelector;
+ object_status: WorkflowStatusType;
+ object_staged_at: Date;
+ object_staged_by: string;
+}
+
+export interface CandidateEntry extends TierEntryDisplayFields {
+ object_ref: string;
+ object_modified: WorkflowRevisionSelector;
+ object_status: WorkflowStatusType;
+ object_added_at: Date;
+ object_added_by: string;
+}
+
+export interface QuarantineEntry {
+ object_ref: string;
+ object_modified: Date;
+ source_track_id: string;
+ source_track_name: string;
+ source_snapshot_version?: string | null;
+ conflict_reason: string;
+}
diff --git a/src/app/classes/serializable.ts b/src/app/classes/serializable.ts
index 286bf37c4..b4bdce1d8 100644
--- a/src/app/classes/serializable.ts
+++ b/src/app/classes/serializable.ts
@@ -1,6 +1,6 @@
import { Observable } from 'rxjs';
import { RestApiConnectorService } from '../services/connectors/rest-api/rest-api-connector.service';
-import { WorkflowState } from '../utils/types';
+import { WorkflowStatusType } from '../utils/types';
/**
* Objects which are serializable to the REST API implement this class
@@ -17,7 +17,7 @@ export abstract class Serializable {
* @abstract
* @param {*} raw the raw object to parse
*/
- public abstract deserialize(raw: any);
+ public abstract deserialize(raw: any): any;
/**
* Validate the current object state and return information on the result of the validation
@@ -29,7 +29,7 @@ export abstract class Serializable {
public abstract validate(
restAPIService: RestApiConnectorService,
options?: any,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable;
}
diff --git a/src/app/classes/stix/analytic.ts b/src/app/classes/stix/analytic.ts
index 9ea63968c..b00d1a1c0 100644
--- a/src/app/classes/stix/analytic.ts
+++ b/src/app/classes/stix/analytic.ts
@@ -3,7 +3,7 @@ import { logger } from '../../utils/logger';
import { Observable, of, switchMap } from 'rxjs';
import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service';
import { ValidationData } from '../serializable';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class Analytic extends StixObject {
public name = '';
@@ -179,7 +179,7 @@ export class Analytic extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState).pipe(
switchMap(result => {
diff --git a/src/app/classes/stix/asset.ts b/src/app/classes/stix/asset.ts
index c6a9add5c..42daeac38 100644
--- a/src/app/classes/stix/asset.ts
+++ b/src/app/classes/stix/asset.ts
@@ -3,7 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re
import { logger } from '../../utils/logger';
import { ValidationData } from '../serializable';
import { StixObject } from './stix-object';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class Asset extends StixObject {
public name = '';
@@ -139,7 +139,7 @@ export class Asset extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState);
}
diff --git a/src/app/classes/stix/campaign-citations.spec.ts b/src/app/classes/stix/campaign-citations.spec.ts
new file mode 100644
index 000000000..c4c4a72e7
--- /dev/null
+++ b/src/app/classes/stix/campaign-citations.spec.ts
@@ -0,0 +1,53 @@
+import { firstValueFrom, of } from 'rxjs';
+import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service';
+import { Campaign } from './campaign';
+
+describe('Campaign citation validation', () => {
+ it('preserves references cited by the first and last seen fields', async () => {
+ const campaign = new Campaign({
+ workspace: {
+ workflow: {
+ state: 'work-in-progress',
+ },
+ },
+ stix: {
+ type: 'campaign',
+ id: 'campaign--46421788-b6e1-4256-b351-f8beffd1afba',
+ created: '2023-09-27T13:11:52.340Z',
+ modified: '2026-07-31T20:48:14.920Z',
+ spec_version: '2.1',
+ name: '2015 Ukraine Electric Power Attack',
+ description: 'Campaign description without citations.',
+ aliases: ['2015 Ukraine Electric Power Attack'],
+ first_seen: '2015-12-01T05:00:00.000Z',
+ last_seen: '2016-01-01T05:00:00.000Z',
+ x_mitre_first_seen_citation: '(Citation: Booz Allen Hamilton)',
+ x_mitre_last_seen_citation: '(Citation: Booz Allen Hamilton)',
+ x_mitre_version: '1.0',
+ external_references: [
+ {
+ source_name: 'mitre-attack',
+ external_id: 'C0028',
+ url: 'https://attack.mitre.org/campaigns/C0028',
+ },
+ {
+ source_name: 'Booz Allen Hamilton',
+ description: 'When The Lights Went Out.',
+ url: 'https://example.com/when-the-lights-went-out.pdf',
+ },
+ ],
+ },
+ });
+ const restApiService = {
+ validateStixObject: () => () => of({ errors: [], warnings: [] }),
+ getAllCampaigns: () => of({ data: [] }),
+ getReference: () => of([]),
+ } as unknown as RestApiConnectorService;
+
+ await firstValueFrom(campaign.validate(restApiService));
+
+ expect(campaign.serialize().stix.external_references).toContainEqual(
+ expect.objectContaining({ source_name: 'Booz Allen Hamilton' })
+ );
+ });
+});
diff --git a/src/app/classes/stix/campaign.ts b/src/app/classes/stix/campaign.ts
index 25d4c081e..e820334b2 100644
--- a/src/app/classes/stix/campaign.ts
+++ b/src/app/classes/stix/campaign.ts
@@ -3,7 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re
import { logger } from '../../utils/logger';
import { ValidationData } from '../serializable';
import { StixObject } from './stix-object';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class Campaign extends StixObject {
public name = '';
@@ -13,6 +13,7 @@ export class Campaign extends StixObject {
public last_seen_citation = '';
public aliases: string[] = [];
public contributors: string[] = [];
+ public domains: string[] = [];
public readonly supportsAttackID = true;
protected get attackIDValidator() {
@@ -47,6 +48,7 @@ export class Campaign extends StixObject {
rep.stix.x_mitre_last_seen_citation = this.last_seen_citation.trim();
rep.stix.aliases = this.aliases.map(x => x.trim());
rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim());
+ rep.stix.x_mitre_domains = this.domains;
// Strip properties that are empty strs + lists
rep.stix = this.filterObject(rep.stix);
@@ -151,6 +153,12 @@ export class Campaign extends StixObject {
')'
);
} else this.contributors = [];
+
+ if ('x_mitre_domains' in sdo) {
+ if (this.isStringArray(sdo.x_mitre_domains))
+ this.domains = sdo.x_mitre_domains;
+ else logger.error('TypeError: domains field is not a string array.');
+ } else this.domains = [];
}
}
@@ -161,7 +169,7 @@ export class Campaign extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState);
}
diff --git a/src/app/classes/stix/canonical-domains.spec.ts b/src/app/classes/stix/canonical-domains.spec.ts
new file mode 100644
index 000000000..834efd03d
--- /dev/null
+++ b/src/app/classes/stix/canonical-domains.spec.ts
@@ -0,0 +1,77 @@
+import {
+ Analytic,
+ Asset,
+ Campaign,
+ DataComponent,
+ DataSource,
+ DetectionStrategy,
+ Group,
+ Matrix,
+ Mitigation,
+ Software,
+ StixObject,
+ Tactic,
+ Technique,
+} from './index';
+
+const domains = ['enterprise-attack', 'ics-attack'];
+
+function rawObject(type: string) {
+ return {
+ workspace: {
+ workflow: {
+ state: 'work-in-progress',
+ },
+ },
+ stix: {
+ type,
+ id: `${type}--00000000-0000-4000-8000-000000000000`,
+ created: '2026-08-03T00:00:00.000Z',
+ modified: '2026-08-03T00:00:00.000Z',
+ spec_version: '2.1',
+ name: 'Domain round-trip fixture',
+ x_mitre_version: '1.0',
+ x_mitre_domains: domains,
+ external_references: [],
+ },
+ };
+}
+
+const targetTypes: {
+ type: string;
+ create: (raw: ReturnType) => StixObject;
+}[] = [
+ { type: 'attack-pattern', create: raw => new Technique(raw) },
+ { type: 'campaign', create: raw => new Campaign(raw) },
+ { type: 'course-of-action', create: raw => new Mitigation(raw) },
+ { type: 'intrusion-set', create: raw => new Group(raw) },
+ { type: 'malware', create: raw => new Software('malware', raw) },
+ { type: 'tool', create: raw => new Software('tool', raw) },
+ { type: 'x-mitre-analytic', create: raw => new Analytic(raw) },
+ { type: 'x-mitre-asset', create: raw => new Asset(raw) },
+ {
+ type: 'x-mitre-data-component',
+ create: raw => new DataComponent(raw),
+ },
+ { type: 'x-mitre-data-source', create: raw => new DataSource(raw) },
+ {
+ type: 'x-mitre-detection-strategy',
+ create: raw => new DetectionStrategy(raw),
+ },
+ { type: 'x-mitre-matrix', create: raw => new Matrix(raw) },
+ { type: 'x-mitre-tactic', create: raw => new Tactic(raw) },
+];
+
+describe('canonical domain-bearing STIX objects', () => {
+ it.each(targetTypes)(
+ 'preserves x_mitre_domains when revising $type',
+ ({ type, create }) => {
+ const object = create(rawObject(type));
+
+ expect((object as StixObject & { domains: string[] }).domains).toEqual(
+ domains
+ );
+ expect(object.serialize().stix.x_mitre_domains).toEqual(domains);
+ }
+ );
+});
diff --git a/src/app/classes/stix/collection.ts b/src/app/classes/stix/collection.ts
index ed9d5ac39..ce6836624 100644
--- a/src/app/classes/stix/collection.ts
+++ b/src/app/classes/stix/collection.ts
@@ -19,7 +19,7 @@ import {
Tactic,
Technique,
} from '../stix';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
/**
* auto-generated changelog/report about an import
@@ -498,7 +498,7 @@ export class Collection extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState);
}
diff --git a/src/app/classes/stix/data-component.ts b/src/app/classes/stix/data-component.ts
index f574be93b..03a3227ee 100644
--- a/src/app/classes/stix/data-component.ts
+++ b/src/app/classes/stix/data-component.ts
@@ -4,7 +4,7 @@ import { ValidationData } from '../serializable';
import { StixObject } from './stix-object';
import { logger } from '../../utils/logger';
import { DataSource } from './data-source';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class DataComponent extends StixObject {
public name = '';
@@ -133,7 +133,7 @@ export class DataComponent extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState);
}
diff --git a/src/app/classes/stix/data-source.ts b/src/app/classes/stix/data-source.ts
index 26c154c2d..be7255988 100644
--- a/src/app/classes/stix/data-source.ts
+++ b/src/app/classes/stix/data-source.ts
@@ -4,7 +4,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re
import { Observable } from 'rxjs';
import { ValidationData } from '../serializable';
import { DataComponent } from './data-component';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class DataSource extends StixObject {
public name = '';
@@ -136,7 +136,7 @@ export class DataSource extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState);
}
diff --git a/src/app/classes/stix/detection-strategy.ts b/src/app/classes/stix/detection-strategy.ts
index a1cfa794a..8f7a0eee6 100644
--- a/src/app/classes/stix/detection-strategy.ts
+++ b/src/app/classes/stix/detection-strategy.ts
@@ -3,12 +3,13 @@ import { logger } from '../../utils/logger';
import { Observable } from 'rxjs';
import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service';
import { ValidationData } from '../serializable';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class DetectionStrategy extends StixObject {
public name = '';
public contributors: string[] = [];
public analytics: string[] = []; // list of x-mitre-analytic uuids
+ public domains: string[] = [];
public readonly supportsAttackID = true;
protected get attackIDValidator() {
@@ -37,6 +38,7 @@ export class DetectionStrategy extends StixObject {
rep.stix.name = this.name.trim();
rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim());
if (this.analytics) rep.stix.x_mitre_analytic_refs = this.analytics;
+ rep.stix.x_mitre_domains = this.domains;
// Strip properties that are empty strs + lists
rep.stix = this.filterObject(rep.stix);
@@ -78,6 +80,12 @@ export class DetectionStrategy extends StixObject {
`TypeError: x_mitre_analytic_refs field is not a string array: ${sdo.x_mitre_analytic_refs} (${typeof sdo.x_mitre_analytic_refs})`
);
} else this.analytics = [];
+
+ if ('x_mitre_domains' in sdo) {
+ if (this.isStringArray(sdo.x_mitre_domains))
+ this.domains = sdo.x_mitre_domains;
+ else logger.error('TypeError: domains field is not a string array.');
+ } else this.domains = [];
}
}
@@ -88,7 +96,7 @@ export class DetectionStrategy extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState);
}
diff --git a/src/app/classes/stix/group.ts b/src/app/classes/stix/group.ts
index bd7e2b6ee..1850b27d5 100644
--- a/src/app/classes/stix/group.ts
+++ b/src/app/classes/stix/group.ts
@@ -3,12 +3,13 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re
import { Observable } from 'rxjs';
import { ValidationData } from '../serializable';
import { logger } from '../../utils/logger';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class Group extends StixObject {
public name = '';
public aliases: string[] = [];
public contributors: string[] = [];
+ public domains: string[] = [];
public readonly supportsAttackID = true;
protected get attackIDValidator() {
@@ -38,6 +39,7 @@ export class Group extends StixObject {
rep.stix.name = this.name.trim();
rep.stix.aliases = this.aliases.map(x => x.trim());
rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim());
+ rep.stix.x_mitre_domains = this.domains;
// Strip properties that are empty strs + lists
rep.stix = this.filterObject(rep.stix);
@@ -90,6 +92,12 @@ export class Group extends StixObject {
')'
);
} else this.contributors = [];
+
+ if ('x_mitre_domains' in sdo) {
+ if (this.isStringArray(sdo.x_mitre_domains))
+ this.domains = sdo.x_mitre_domains;
+ else logger.error('TypeError: domains field is not a string array.');
+ } else this.domains = [];
}
}
@@ -100,7 +108,7 @@ export class Group extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState);
}
diff --git a/src/app/classes/stix/identity.ts b/src/app/classes/stix/identity.ts
index 5a462ce61..7a3ce186b 100644
--- a/src/app/classes/stix/identity.ts
+++ b/src/app/classes/stix/identity.ts
@@ -3,7 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re
import { logger } from '../../utils/logger';
import { ValidationData } from '../serializable';
import { StixObject } from './stix-object';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class Identity extends StixObject {
public name: string; // identity name
@@ -118,8 +118,9 @@ export class Identity extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- _tempWorkflowState?: WorkflowState
+ _tempWorkflowState?: WorkflowStatusType
): Observable {
+ void _tempWorkflowState;
return this.base_validate(restAPIService);
}
diff --git a/src/app/classes/stix/marking-definition.ts b/src/app/classes/stix/marking-definition.ts
index 458e7e744..dc4cd1902 100644
--- a/src/app/classes/stix/marking-definition.ts
+++ b/src/app/classes/stix/marking-definition.ts
@@ -4,7 +4,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re
import { Observable, of } from 'rxjs';
import { ValidationData } from '../serializable';
import { logger } from '../../utils/logger';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class MarkingDefinition extends StixObject {
public name = '';
@@ -104,7 +104,7 @@ export class MarkingDefinition extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
return this.base_validate(restAPIService, tempWorkflowState).pipe(
map(result => {
diff --git a/src/app/classes/stix/matrix.ts b/src/app/classes/stix/matrix.ts
index 1e62fdd78..f9264053f 100644
--- a/src/app/classes/stix/matrix.ts
+++ b/src/app/classes/stix/matrix.ts
@@ -4,11 +4,12 @@ import { ValidationData } from '../serializable';
import { StixObject } from './stix-object';
import { logger } from '../../utils/logger';
import { Tactic } from './tactic';
-import { WorkflowState } from 'src/app/utils/types';
+import { WorkflowStatusType } from 'src/app/utils/types';
export class Matrix extends StixObject {
public name = '';
public tactic_refs: string[] = [];
+ public domains: string[] = [];
// NOTE: this is only populated in the matrix view when calling getTechniquesInMatrix() NOT getMatrix()
public tactic_objects: Tactic[] = [];
@@ -40,6 +41,7 @@ export class Matrix extends StixObject {
rep.stix.name = this.name.trim();
rep.stix.tactic_refs = this.tactic_refs;
+ rep.stix.x_mitre_domains = this.domains;
// Strip properties that are empty strs + lists
rep.stix = this.filterObject(rep.stix);
@@ -80,6 +82,12 @@ export class Matrix extends StixObject {
')'
);
} else this.tactic_refs = [];
+
+ if ('x_mitre_domains' in sdo) {
+ if (this.isStringArray(sdo.x_mitre_domains))
+ this.domains = sdo.x_mitre_domains;
+ else logger.error('TypeError: domains field is not a string array.');
+ } else this.domains = [];
}
}
@@ -90,7 +98,7 @@ export class Matrix extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
- tempWorkflowState?: WorkflowState
+ tempWorkflowState?: WorkflowStatusType
): Observable {
// TODO verify all tactics exist
return this.base_validate(restAPIService, tempWorkflowState);
@@ -116,6 +124,7 @@ export class Matrix extends StixObject {
public delete(_restAPIService: RestApiConnectorService): Observable