From 327d5034d3192dcfd79ce91c99067d5fda528f7f Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Mon, 10 Aug 2026 09:48:01 -0400 Subject: [PATCH 1/6] feat(registry-org): add ROOT metadata, CNA last-resort flag, and integration coverage --- schemas/registry-org/CNAOrg.json | 3 ++ schemas/registry-org/RootOrg.json | 9 ++++ .../create-registry-org-request.json | 8 +++ .../create-registry-org-response.json | 8 +++ .../get-registry-org-response.json | 8 +++ .../list-registry-orgs-response.json | 8 +++ .../update-registry-org-request.json | 8 +++ .../update-registry-org-response.json | 8 +++ src/constants/index.js | 4 +- src/model/cnaorg.js | 1 + src/model/rootorg.js | 5 +- .../registry-org/registryOrgCRUDTest.js | 19 ++++++- .../registry-org/rootOrgTest.js | 50 ++++++++++++++++++- 13 files changed, 134 insertions(+), 5 deletions(-) diff --git a/schemas/registry-org/CNAOrg.json b/schemas/registry-org/CNAOrg.json index c852f1e79..8e32aa932 100644 --- a/schemas/registry-org/CNAOrg.json +++ b/schemas/registry-org/CNAOrg.json @@ -89,6 +89,9 @@ "minimum": 0, "maximum": 100000 }, + "is_last_resort": { + "type": "boolean" + }, "oversees": { "type": "array", "uniqueItems": true, diff --git a/schemas/registry-org/RootOrg.json b/schemas/registry-org/RootOrg.json index a6e8a9d7e..a60691ee5 100644 --- a/schemas/registry-org/RootOrg.json +++ b/schemas/registry-org/RootOrg.json @@ -107,6 +107,15 @@ }, "top_level_root": { "$ref": "/BaseOrg#/properties/top_level_root" + }, + "is_top_level_root": { + "type": "boolean" + }, + "charter_or_scope": { + "type": "string" + }, + "disclosure_policy": { + "type": "string" } }, "required": [ diff --git a/schemas/registry-org/create-registry-org-request.json b/schemas/registry-org/create-registry-org-request.json index b1917cf18..2e583abc4 100644 --- a/schemas/registry-org/create-registry-org-request.json +++ b/schemas/registry-org/create-registry-org-request.json @@ -44,6 +44,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort." + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/create-registry-org-response.json b/schemas/registry-org/create-registry-org-response.json index 2071119a1..5161b509d 100644 --- a/schemas/registry-org/create-registry-org-response.json +++ b/schemas/registry-org/create-registry-org-response.json @@ -77,6 +77,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/get-registry-org-response.json b/schemas/registry-org/get-registry-org-response.json index 86fc75326..a1ba1fc1a 100644 --- a/schemas/registry-org/get-registry-org-response.json +++ b/schemas/registry-org/get-registry-org-response.json @@ -41,6 +41,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "reports_to": { "type": [ "string", diff --git a/schemas/registry-org/list-registry-orgs-response.json b/schemas/registry-org/list-registry-orgs-response.json index e4956e545..40bc74add 100644 --- a/schemas/registry-org/list-registry-orgs-response.json +++ b/schemas/registry-org/list-registry-orgs-response.json @@ -70,6 +70,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "reports_to": { "type": [ "string", diff --git a/schemas/registry-org/update-registry-org-request.json b/schemas/registry-org/update-registry-org-request.json index f388a40f7..60cbe400a 100644 --- a/schemas/registry-org/update-registry-org-request.json +++ b/schemas/registry-org/update-registry-org-request.json @@ -60,6 +60,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/update-registry-org-response.json b/schemas/registry-org/update-registry-org-response.json index 4662e4872..387ef9f87 100644 --- a/schemas/registry-org/update-registry-org-response.json +++ b/schemas/registry-org/update-registry-org-response.json @@ -66,6 +66,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/src/constants/index.js b/src/constants/index.js index 4a6c1d38b..89eb0c8a8 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], @@ -55,6 +55,8 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', + 'is_top_level_root', + 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: { diff --git a/src/model/cnaorg.js b/src/model/cnaorg.js index 2e7519c1e..20a7e083a 100644 --- a/src/model/cnaorg.js +++ b/src/model/cnaorg.js @@ -14,6 +14,7 @@ const validate = ajv.compile(CnaOrgSchema) const schema = { oversees: [String], id_quota: Number, + is_last_resort: Boolean, charter_or_scope: String, disclosure_policy: String, product_list: String diff --git a/src/model/rootorg.js b/src/model/rootorg.js index 1517ec65c..ad4dbeea9 100644 --- a/src/model/rootorg.js +++ b/src/model/rootorg.js @@ -12,7 +12,10 @@ ajv.addSchema(BaseOrgSchema) const validate = ajv.compile(RootOrgSchema) const schema = { - oversees: [String] + oversees: [String], + is_top_level_root: Boolean, + charter_or_scope: String, + disclosure_policy: String } const options = { discriminatorKey: 'kind' } diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js index 8b0cf5320..c8d6b21db 100644 --- a/test/integration-tests/registry-org/registryOrgCRUDTest.js +++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js @@ -19,7 +19,8 @@ const testRegistryOrg = { partner_number: 'Initial Partner Number', partner_country: 'US', advisory_locations: ['https://example.com/advisories'], - charter_or_scope: 'This is a normal string, not a URI' + charter_or_scope: 'This is a normal string, not a URI', + is_last_resort: true } let createdOrg @@ -82,6 +83,9 @@ describe('Testing /registry/org endpoints', () => { expect(res.body.created).to.haveOwnProperty('charter_or_scope') expect(res.body.created.charter_or_scope).to.equal(testRegistryOrg.charter_or_scope) + expect(res.body.created).to.haveOwnProperty('is_last_resort') + expect(res.body.created.is_last_resort).to.equal(true) + expect(res.body.created).to.haveOwnProperty('program_data') expect(res.body.created.program_data.status).to.equal('inactive') expect(res.body.created.program_data).to.haveOwnProperty('partner_inactive_date') @@ -671,7 +675,8 @@ describe('Testing /registry/org endpoints', () => { partner_role_type: ['Researcher', 'Vendor'], partner_number: 'Updated Partner Number', partner_country: 'UK', - advisory_locations: ['https://example.com/updated_advisories'] + advisory_locations: ['https://example.com/updated_advisories'], + is_last_resort: false }) .then((res, err) => { expect(err).to.be.undefined @@ -708,6 +713,16 @@ describe('Testing /registry/org endpoints', () => { expect(res.body.updated).to.haveOwnProperty('advisory_locations') expect(res.body.updated.advisory_locations).to.deep.equal(['https://example.com/updated_advisories']) + expect(res.body.updated.is_last_resort).to.equal(false) + }) + }) + it('Retrieves the CNA last-resort flag', async () => { + await chai.request(app) + .get('/api/registry/org/registry_org_test') + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.is_last_resort).to.equal(false) }) }) it('Allows Secretariat to update program_data', async () => { diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 18c4bd7e1..5774cb011 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -13,7 +13,10 @@ let rootAdminHeaders const testRootOrg = { short_name: 'root_org_test_4', long_name: 'Root Org Test', - authority: ['ROOT'] + authority: ['ROOT'], + is_top_level_root: true, + charter_or_scope: 'Coordinates CVE Numbering Authorities.', + disclosure_policy: 'Discloses vulnerabilities after coordinated remediation.' } let createdOrg let reportingOrg @@ -31,6 +34,9 @@ describe('Testing ROOT Organization Type', () => { expect(res).to.have.status(200) expect(res.body.message).to.equal(testRootOrg.short_name + ' organization was successfully created.') expect(res.body.created.authority).to.deep.equal(['ROOT']) + expect(res.body.created.is_top_level_root).to.equal(true) + expect(res.body.created.charter_or_scope).to.equal(testRootOrg.charter_or_scope) + expect(res.body.created.disclosure_policy).to.equal(testRootOrg.disclosure_policy) createdOrg = res.body.created delete createdOrg.created delete createdOrg.last_updated @@ -55,6 +61,34 @@ describe('Testing ROOT Organization Type', () => { expect(res.body.message).to.equal('Parameters were invalid') }) }) + + it('Secretariat can update and retrieve ROOT-specific fields', async () => { + await chai.request(app) + .put(`/api/registry/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .send({ + ...createdOrg, + is_top_level_root: false, + charter_or_scope: 'Updated ROOT charter.', + disclosure_policy: 'Updated ROOT disclosure policy.' + }) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.updated.is_top_level_root).to.equal(false) + expect(res.body.updated.charter_or_scope).to.equal('Updated ROOT charter.') + expect(res.body.updated.disclosure_policy).to.equal('Updated ROOT disclosure policy.') + }) + + await chai.request(app) + .get(`/api/registry/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.is_top_level_root).to.equal(false) + expect(res.body.charter_or_scope).to.equal('Updated ROOT charter.') + expect(res.body.disclosure_policy).to.equal('Updated ROOT disclosure policy.') + }) + }) }) context('Managing reports-to relationships', () => { @@ -238,6 +272,20 @@ describe('Testing ROOT Organization Type', () => { }) }) + it('ROOT admin cannot edit is_top_level_root', async () => { + await chai.request(app) + .put(`/api/registry/org/${testRootOrg.short_name}`) + .set(rootAdminHeaders) + .send({ + ...createdOrg, + is_top_level_root: true + }) + .then((res) => { + expect(res).to.have.status(403) + expect(res.body.error).to.equal('SECRETARIAT_ONLY') + }) + }) + it('ROOT admin cannot set oversees through the standard PUT endpoint', async () => { await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) From 2cf2dfef700f9b591e73e1a243a33ed3f0bddd38 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Mon, 10 Aug 2026 10:46:09 -0400 Subject: [PATCH 2/6] Removing secretariat only fields from joint approval array --- src/constants/index.js | 2 +- test/integration-tests/registry-org/rootOrgTest.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 89eb0c8a8..c4db3affc 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 5774cb011..05f9d1fba 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -245,11 +245,13 @@ describe('Testing ROOT Organization Type', () => { }) it('ROOT admin can update their own org', async () => { + const rootAdminPayload = { ...createdOrg } + delete rootAdminPayload.is_top_level_root await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) .send({ - ...createdOrg, + ...rootAdminPayload, long_name: 'Updated Root Org Test' }) .then((res) => { @@ -287,11 +289,13 @@ describe('Testing ROOT Organization Type', () => { }) it('ROOT admin cannot set oversees through the standard PUT endpoint', async () => { + const rootAdminPayload = { ...createdOrg } + delete rootAdminPayload.is_top_level_root await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) .send({ - ...createdOrg, + ...rootAdminPayload, oversees: [reportingOrg.UUID] }) .then((res) => { From b6264c544739158cff7a2d7223449f8988b6e98f Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 10:14:56 -0400 Subject: [PATCH 3/6] version number bump --- api-docs/openapi.json | 2 +- package.json | 2 +- src/swagger.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 84fe59bcf..3031f2cf1 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.8.4", + "version": "2.8.5", "title": "CVE Services API", "description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:
  • If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials
  • Contact your Root (Google, INCIBE, JPCERT/CC, or Red Hat) or Top-Level Root (CISA ICS or MITRE) to request credentials

CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located here.

Contact the CVE Services team", "contact": { diff --git a/package.json b/package.json index 00409c63f..b63447db2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.8.4", + "version": "2.8.5", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/src/swagger.js b/src/swagger.js index 5edc22173..fd775f5cd 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -21,7 +21,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re /* eslint-disable no-multi-str */ const doc = { info: { - version: '2.8.4', + version: '2.8.5', title: 'CVE Services API', description: "The CVE Services API supports automation tooling for the CVE Program. Credentials are \ required for most service endpoints. Representatives of \ From 25a1b5c0d4707789e39a1106cf62e9d24b5245fa Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 10:14:56 -0400 Subject: [PATCH 4/6] version number bump --- api-docs/openapi.json | 2 +- package.json | 2 +- src/swagger.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 84fe59bcf..3031f2cf1 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.8.4", + "version": "2.8.5", "title": "CVE Services API", "description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:
  • If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials
  • Contact your Root (Google, INCIBE, JPCERT/CC, or Red Hat) or Top-Level Root (CISA ICS or MITRE) to request credentials

CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located here.

Contact the CVE Services team", "contact": { diff --git a/package.json b/package.json index 00409c63f..b63447db2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.8.4", + "version": "2.8.5", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/src/swagger.js b/src/swagger.js index 5edc22173..fd775f5cd 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -21,7 +21,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re /* eslint-disable no-multi-str */ const doc = { info: { - version: '2.8.4', + version: '2.8.5', title: 'CVE Services API', description: "The CVE Services API supports automation tooling for the CVE Program. Credentials are \ required for most service endpoints. Representatives of \ From 771cb6719f689182cf6a7ff89c45234ca66ee37e Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 12:27:41 -0400 Subject: [PATCH 5/6] feat(org): require joint approval for last-resort and top-level flags --- src/constants/index.js | 4 +--- src/controller/registry.controller/index.js | 2 ++ .../registry-org/rootOrgTest.js | 16 +++++++++++++--- .../review-object/reviewObjectTest.js | 2 ++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index c4db3affc..3bc1dff19 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], @@ -55,8 +55,6 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', - 'is_top_level_root', - 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: { diff --git a/src/controller/registry.controller/index.js b/src/controller/registry.controller/index.js index 2735f8cdf..7350a564d 100644 --- a/src/controller/registry.controller/index.js +++ b/src/controller/registry.controller/index.js @@ -557,6 +557,8 @@ router.put('/registry/org/:shortname',
  • aliases
  • oversees
  • top_level_root
  • +
  • is_top_level_root
  • +
  • is_last_resort
  • charter_or_scope
  • product_list
  • disclosure_policy
  • diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 05f9d1fba..e61254eaf 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -274,7 +274,7 @@ describe('Testing ROOT Organization Type', () => { }) }) - it('ROOT admin cannot edit is_top_level_root', async () => { + it('ROOT admin can request a change to is_top_level_root through joint approval', async () => { await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) @@ -283,8 +283,18 @@ describe('Testing ROOT Organization Type', () => { is_top_level_root: true }) .then((res) => { - expect(res).to.have.status(403) - expect(res.body.error).to.equal('SECRETARIAT_ONLY') + expect(res).to.have.status(200) + expect(res.body.message).to.include('joint approval is required') + expect(res.body.updated.is_top_level_root).to.equal(false) + }) + + return chai.request(app) + .get(`/api/review/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.status).to.equal('pending') + expect(res.body).to.have.nested.property('new_review_data.is_top_level_root', true) }) }) diff --git a/test/integration-tests/review-object/reviewObjectTest.js b/test/integration-tests/review-object/reviewObjectTest.js index d9fbe661f..a65412170 100644 --- a/test/integration-tests/review-object/reviewObjectTest.js +++ b/test/integration-tests/review-object/reviewObjectTest.js @@ -215,6 +215,7 @@ describe('Review Object Controller Integration Tests', () => { updateData.long_name = 'Approve Test Organization' updateData.authority = ['CNA'] updateData.id_quota = 1000 + updateData.is_last_resort = true updateData.contact_info = { websites: ['https://www.example.com'] } const res = await chai .request(app) @@ -232,6 +233,7 @@ describe('Review Object Controller Integration Tests', () => { expect(reviewRes.body).to.have.property('uuid') expect(reviewRes.body.status).to.equal('pending') expect(reviewRes.body).to.have.nested.property('new_review_data.long_name', 'Approve Test Organization') + expect(reviewRes.body).to.have.nested.property('new_review_data.is_last_resort', true) expect(reviewRes.body.new_review_data.contact_info.websites[0]).to.equal('https://www.example.com') approveTestReviewUUID = reviewRes.body.uuid }) From e8554632261b63ade67a879a666e712b9b9e676c Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 14:06:58 -0400 Subject: [PATCH 6/6] j-a fields --- src/constants/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 89eb0c8a8..3bc1dff19 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -55,8 +55,6 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', - 'is_top_level_root', - 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: {