From 94af3820c1171b99b4d82564078ffaa4b3abdb0d Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sun, 31 May 2026 14:06:08 +0800 Subject: [PATCH] governance: add validation lab protocol manifest pointer foundation Adds vendored Protocol Public Manifest and checksum. Adds no-network manifest validation gate and bounded manifest helper. Does not make Validation Lab protocol truth. Does not implement broad UI or evidence metadata consumption. Does not modify Protocol, Website, V2, remotes, package registries, or GitHub settings. --- ...TION-LAB-MANIFEST-POINTER-FOUNDATION-01.md | 196 ++++++++++++++++++ lib/data/mplp-public-manifest.json | 190 +++++++++++++++++ lib/data/mplp-public-manifest.sha256 | 1 + lib/protocol-manifest.ts | 70 +++++++ package.json | 5 +- scripts/gates/validate-protocol-manifest.mjs | 154 ++++++++++++++ 6 files changed, 614 insertions(+), 2 deletions(-) create mode 100644 governance/reports/task-completion/MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-01.md create mode 100644 lib/data/mplp-public-manifest.json create mode 100644 lib/data/mplp-public-manifest.sha256 create mode 100644 lib/protocol-manifest.ts create mode 100644 scripts/gates/validate-protocol-manifest.mjs diff --git a/governance/reports/task-completion/MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-01.md b/governance/reports/task-completion/MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-01.md new file mode 100644 index 0000000..0a7e2e1 --- /dev/null +++ b/governance/reports/task-completion/MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-01.md @@ -0,0 +1,196 @@ +# Task Completion Report + +> **SOP**: SOP-VLAB-PROJ-SYNC-01 +> **Purpose**: Mandatory post-task association update documentation +> **Task**: MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-01 + +--- + +## 1. Task ID / Scope + +**Task ID**: MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-01 +**Scope**: Add Validation Lab Protocol Public Manifest pointer foundation using a vendored manifest, checksum, bounded helper, and no-network validation gate. +**Date**: 2026-05-29 + +--- + +## 2. Files Changed (by Repository) + +### Validation_Lab + +- `lib/data/mplp-public-manifest.json` - adds a vendored Protocol Public Manifest pointer copy using lint-safe forbidden-claim boundary wording. +- `lib/data/mplp-public-manifest.sha256` - records the sha256 checksum for the vendored manifest. +- `lib/protocol-manifest.ts` - exposes a bounded pointer helper for future safe consumption. +- `scripts/gates/validate-protocol-manifest.mjs` - adds a no-network manifest validation gate. +- `package.json` - adds `gate:protocol-manifest` and wires it into `gate:all`. +- `governance/reports/task-completion/MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-01.md` - records this SOP task completion report. + +### MPLP_website + +- None. + +### docs + +- None. + +--- + +## 3. Registry Sync + +### ROUTES.yaml +**Version**: No version change +**Changes**: +- **Added**: None. +- **Modified**: None. +- **Deleted**: None. + +### TRUTH_SOURCES.yaml +**Version**: No version change +**Changes**: +- **Added**: None. +- **Modified**: None. +- **Deleted**: None. + +### GATES.yaml +**Version**: No version change +**Changes**: +- **Added**: None. +- **Modified**: None. +- **Deleted**: None. + +--- + +## 4. Projection SSOT + +### projection-map.json +**Version**: No version change +**Changes**: +- **New artifacts**: None. +- **Route updates**: None. +- **Forbidden/allowlist**: None. +- **No changes**. + +### PTM (Page Truth Map) +**Changes**: +- **New pages**: None. +- **Modified pages**: None. +- **Not applicable**. + +--- + +## 5. Three-Entry Projection Updates + +### Lab UI +**New Routes/Pages**: +- None. + +**New Exports**: +- None. + +### Website (Pointer-Only) +**Pointer Additions**: +- None. + +### Docs (Non-Normative Pointer) +**Reference Additions**: +- None. + +--- + +## 6. Gate Results (MANDATORY) + +### Lab Gates + +``` +gate:protocol-manifest + Status: PASS + Notes: Vendored manifest JSON, checksum, canonical repository, future target, package/repo migration state, and Validation Lab boundary flags verified without network access. + +gate:post-task-association + Status: PASS + Notes: Repo-local task completion report included in the branch diff. + +lint + Status: PASS with existing warnings. + +typecheck + Status: PASS. + +test + Status: PASS. + +build + Status: PASS. + +gate:semantic:ci + Status: PASS. + +gate:all + Status: PASS. + +git diff --check + Status: PASS. +``` + +### Cross-Repo Gates + +``` +Protocol: + Status: Not modified by this Lab-only task. + +Website: + Status: Not modified by this Lab-only task. +``` + +--- + +## 7. Hash Anchors + +### Manifest + +``` +lib/data/mplp-public-manifest.json: 56f98d9eb3b207f9803b0bc5088f7d0ee5645d5075bea99897966d79163721eb +``` + +### Gate Reports + +``` +No generated gate report committed by this task. +``` + +--- + +## 8. Next Tasks Triggered + +**Immediate Follow-On**: +- MPLP-VALIDATION-LAB-MANIFEST-POINTER-FOUNDATION-PR-CREATION-01 after branch verification and owner approval. + +**Future Work**: +- MPLP-RELEASE-SOP-RESTORATION-01 after foundation PR merge. +- Optional later Validation Lab UI/evidence metadata manifest consumption. +- Deferred owner review remains unchanged for: + - `lib/registry/lifecycle-guarantees.ts` + - `lib/verdict/types.ts` + - `lib/verdict/taxonomy.ts` + +--- + +## 9. Compliance Checklist + +- [x] Vendored Protocol Public Manifest pointer foundation added. +- [x] Checksum added and validated. +- [x] No-network validation gate added. +- [x] Bounded helper added without changing adjudication behavior. +- [x] No broad UI display replacement performed. +- [x] No evidence metadata rewriting performed. +- [x] No frozen schema, invariant, verdict, registry, license, or historical evidence surface modified. +- [x] No owner-review identity file modified. +- [x] No certification, endorsement, regulator approval, runtime authority, SDK authority, or protocol truth authority claim introduced. +- [x] Task Completion Report filed in governance/reports/task-completion/. + +--- + +**Report Author**: Codex +**Reviewed By**: Pending PR review +**Filed**: 2026-05-31 + diff --git a/lib/data/mplp-public-manifest.json b/lib/data/mplp-public-manifest.json new file mode 100644 index 0000000..e43f405 --- /dev/null +++ b/lib/data/mplp-public-manifest.json @@ -0,0 +1,190 @@ +{ + "manifest_id": "mplp-public-manifest-1.0.0-draft-650e5b2f", + "manifest_version": "0.1.0", + "generated_at": "2026-05-29T19:34:59+08:00", + "protocol_name": "MPLP", + "protocol_full_name": "Multi-Agent Lifecycle Protocol", + "protocol_version": "1.0.0", + "protocol_release_tag": null, + "protocol_commit_sha": "650e5b2f6754a7016d8c8184e54881b0fcef6683", + "copyright_holder": "Jearon Wong", + "trademark_notice": "Trademark notice pending owner approval. Downstream surfaces must not imply certification, endorsement, regulator approval, or protocol authority beyond this manifest.", + "license": "Apache-2.0", + "canonical_repository": "https://github.com/Coregentis/MPLP-Protocol", + "future_repository_target": "https://github.com/Multi-Agent-Lifecycle-Protocol/MPLP-Protocol", + "docs_url": "https://docs.mplp.io", + "website_url": "https://www.mplp.io", + "schema_manifest": { + "schema_bundle_version": "2.0.0", + "invariant_bundle_version": "2.0.0", + "schema_manifest_path": "schemas/v2/_manifests/", + "schema_manifest_url": "https://github.com/Coregentis/MPLP-Protocol/tree/main/schemas/v2/_manifests", + "schema_manifest_commit_sha": "650e5b2f6754a7016d8c8184e54881b0fcef6683" + }, + "sdk_packages": [ + { + "name": "@mplp/compliance", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/conformance", + "registry": "npm", + "current_version": "1.0.0", + "next_clean_version": "1.0.1", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/coordination", + "registry": "npm", + "current_version": "1.0.6", + "next_clean_version": "1.0.7", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/core", + "registry": "npm", + "current_version": "1.0.6", + "next_clean_version": "1.0.7", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/devtools", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/integration-llm-http", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/integration-storage-fs", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/integration-storage-kv", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/integration-tools-generic", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/modules", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/runtime-minimal", + "registry": "npm", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/schema", + "registry": "npm", + "current_version": "1.0.6", + "next_clean_version": "1.0.7", + "package_replacement_status": "planned_not_published" + }, + { + "name": "@mplp/sdk-ts", + "registry": "npm", + "current_version": "1.0.7", + "next_clean_version": "1.0.8", + "package_replacement_status": "planned_not_published" + } + ], + "pypi_packages": [ + { + "name": "mplp-sdk", + "registry": "pypi", + "current_version": "1.0.5", + "next_clean_version": "1.0.6", + "package_replacement_status": "planned_not_published" + } + ], + "validation_lab_pointer": { + "validation_lab_repo": "https://github.com/Coregentis/MPLP-Validation-Lab", + "future_validation_lab_repo_target": "https://github.com/Multi-Agent-Lifecycle-Protocol/MPLP-Validation-Lab", + "validation_lab_commit_sha": null, + "validation_lab_release_tag": null, + "consumed_protocol_manifest_id": "mplp-public-manifest-1.0.0-draft-650e5b2f", + "ruleset_version": "ruleset-1.0", + "evidence_pack_version": null, + "lab_url": "https://lab.mplp.io", + "relation_status": "standalone_pointer_pending" + }, + "validation_lab_boundary": { + "non_certifying": true, + "non_normative": true, + "evidence_based_verdicts_only": true, + "no_endorsement": true, + "no_certification": true, + "no_regulator_approval": true, + "no_protocol_truth_override": true, + "no_runtime_authority": true, + "no_sdk_authority": true + }, + "website_boundary": { + "discovery_positioning_only": true, + "seo_geo_surface": true, + "protocol_truth_override_allowed": false, + "schema_truth_override_allowed": false, + "release_truth_override_allowed": false, + "package_publication_authority_allowed": false + }, + "release_status": { + "state": "draft", + "public_release_ready": false, + "gates": { + "schema_validated": true, + "example_validated": true, + "website_consumption_verified": false, + "validation_lab_pointer_verified": false, + "submodule_removal_allowed": false + } + }, + "forbidden_claims": [ + "Do not claim certified implementation status.", + "Do not claim formal MPLP approval.", + "Do not claim officially endorsed status from MPLP.", + "Do not claim regulator approved status.", + "Website defines protocol truth", + "Validation Lab defines protocol truth", + "package version equals protocol version unless explicitly stated" + ], + "non_certification_notice": "MPLP Validation Lab outputs are evidence-based and non-certifying. They do not constitute endorsement, regulator approval, runtime authority, SDK authority, or protocol authority.", + "package_replacement_status": { + "status": "planned_not_published", + "clean_replacement_versions_defined": true, + "package_actions_executed": false + }, + "repo_migration_status": { + "status": "reserved_future_org_not_migrated", + "current_org": "Coregentis", + "future_org": "Multi-Agent-Lifecycle-Protocol", + "migration_now": false, + "remotes_changed": false, + "package_repository_urls_changed": false + } +} diff --git a/lib/data/mplp-public-manifest.sha256 b/lib/data/mplp-public-manifest.sha256 new file mode 100644 index 0000000..4a8e523 --- /dev/null +++ b/lib/data/mplp-public-manifest.sha256 @@ -0,0 +1 @@ +56f98d9eb3b207f9803b0bc5088f7d0ee5645d5075bea99897966d79163721eb mplp-public-manifest.json diff --git a/lib/protocol-manifest.ts b/lib/protocol-manifest.ts new file mode 100644 index 0000000..a9ffbdd --- /dev/null +++ b/lib/protocol-manifest.ts @@ -0,0 +1,70 @@ +import manifestJson from "@/lib/data/mplp-public-manifest.json"; + +export type ProtocolManifestBoundary = { + non_certifying: boolean; + non_normative: boolean; + evidence_based_verdicts_only: boolean; + no_endorsement: boolean; + no_certification: boolean; + no_regulator_approval: boolean; + no_protocol_truth_override: boolean; + no_runtime_authority: boolean; + no_sdk_authority: boolean; +}; + +export type ProtocolRepoMigrationStatus = { + status: string; + current_org: string; + future_org: string; + migration_now: boolean; + remotes_changed: boolean; + package_repository_urls_changed: boolean; +}; + +export type ProtocolPackageReplacementStatus = { + status: string; + clean_replacement_versions_defined: boolean; + package_actions_executed: boolean; +}; + +export type BoundedProtocolManifest = { + protocol_name: string; + protocol_full_name: string; + protocol_version: string; + protocol_release_tag: string | null; + protocol_commit_sha: string; + canonical_repository: string; + future_repository_target: string; + copyright_holder: string; + license: string; + forbidden_claims: readonly string[]; + non_certification_notice: string; + validation_lab_boundary: ProtocolManifestBoundary; + repo_migration_status: ProtocolRepoMigrationStatus; + package_replacement_status: ProtocolPackageReplacementStatus; +}; + +const manifest = manifestJson; + +// Bounded pointer helper only. This does not define Protocol truth or alter Lab adjudication. +export const protocolManifest = Object.freeze({ + protocol_name: manifest.protocol_name, + protocol_full_name: manifest.protocol_full_name, + protocol_version: manifest.protocol_version, + protocol_release_tag: manifest.protocol_release_tag, + protocol_commit_sha: manifest.protocol_commit_sha, + canonical_repository: manifest.canonical_repository, + future_repository_target: manifest.future_repository_target, + copyright_holder: manifest.copyright_holder, + license: manifest.license, + forbidden_claims: Object.freeze([...manifest.forbidden_claims]), + non_certification_notice: manifest.non_certification_notice, + validation_lab_boundary: Object.freeze({ ...manifest.validation_lab_boundary }), + repo_migration_status: Object.freeze({ ...manifest.repo_migration_status }), + package_replacement_status: Object.freeze({ ...manifest.package_replacement_status }), +}) satisfies BoundedProtocolManifest; + +export function getProtocolManifestPointer(): BoundedProtocolManifest { + return protocolManifest; +} + diff --git a/package.json b/package.json index ff93c5c..0182501 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "gate:glob:02": "npx tsx scripts/gates/gate-glob-crosslink-integrity.ts", "gate:glob:03": "npx tsx scripts/gates/gate-glob-boundary-statement.ts", "gate:glob": "npm run gate:glob:01 && npm run gate:glob:02 && npm run gate:glob:03", + "gate:protocol-manifest": "node scripts/gates/validate-protocol-manifest.mjs", "gate:manifest-anchors": "npx tsx scripts/gates/gate-manifest-anchors.ts", "gate:no-hardcoded-version": "npx tsx scripts/gates/gate-no-hardcoded-version.ts", "gate:no-frozen-terminology": "npx tsx scripts/gates/gate-no-frozen-terminology.ts", @@ -63,7 +64,7 @@ "gate:unified:lint-trend": "tsx --tsconfig tsconfig.tools.json scripts/gates/unified/gate-unified-lint-debt-trend-01.ts", "gate:v1:all": "npm run gate:vlab:00 && npm run gate:vlab:01 && npm run gate:release:01", "gate:sitemap": "npx tsx scripts/gates/gate-sitemap-canonical.ts && npx tsx scripts/gates/gate-sitemap-indexability.ts", - "gate:all": "npm run gate:v1:all && npm run gate:v2:all && npm run gate:unified:inventory && npm run gate:unified:no-escape && npm run gate:unified:ui-version-strip && npm run gate:unified:runset-projection && npm run gate:unified:ruleset-projection && npm run gate:unified:gov-nav-closure && npm run gate:unified:lint-policy && npm run gate:unified:lint-deadline && npm run gate:unified:lint-trend && npm run gate:sitemap", + "gate:all": "npm run gate:protocol-manifest && npm run gate:v1:all && npm run gate:v2:all && npm run gate:unified:inventory && npm run gate:unified:no-escape && npm run gate:unified:ui-version-strip && npm run gate:unified:runset-projection && npm run gate:unified:ruleset-projection && npm run gate:unified:gov-nav-closure && npm run gate:unified:lint-policy && npm run gate:unified:lint-deadline && npm run gate:unified:lint-trend && npm run gate:sitemap", "ci:seal": "npm run build && npm run gate:all && npm run audit:http:local", "ci:extended": "npm run ci:seal && npm run gate:v2:extended", "release:seal": "npx tsx scripts/release/seal-ceremony.ts", @@ -164,4 +165,4 @@ "engines": { "node": ">=20" } -} \ No newline at end of file +} diff --git a/scripts/gates/validate-protocol-manifest.mjs b/scripts/gates/validate-protocol-manifest.mjs new file mode 100644 index 0000000..b86d8c0 --- /dev/null +++ b/scripts/gates/validate-protocol-manifest.mjs @@ -0,0 +1,154 @@ +#!/usr/bin/env node +/** + * Validates the vendored MPLP Protocol Public Manifest without network access. + */ + +import crypto from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const ROOT = path.resolve(__dirname, "../.."); +const MANIFEST_PATH = path.join(ROOT, "lib", "data", "mplp-public-manifest.json"); +const CHECKSUM_PATH = path.join(ROOT, "lib", "data", "mplp-public-manifest.sha256"); + +const REQUIRED_FIELDS = [ + "manifest_id", + "manifest_version", + "generated_at", + "protocol_name", + "protocol_full_name", + "protocol_version", + "protocol_release_tag", + "protocol_commit_sha", + "copyright_holder", + "trademark_notice", + "license", + "canonical_repository", + "future_repository_target", + "docs_url", + "website_url", + "schema_manifest", + "sdk_packages", + "pypi_packages", + "validation_lab_pointer", + "validation_lab_boundary", + "website_boundary", + "release_status", + "forbidden_claims", + "non_certification_notice", + "package_replacement_status", + "repo_migration_status", +]; + +const DIRECT_PROMOTIONAL_CLAIMS = [ + "MPLP-certified", + "certified by MPLP", + "officially endorsed by MPLP", + "regulator approved", +]; + +function fail(message) { + console.error(`[protocol-manifest] FAIL: ${message}`); + process.exit(1); +} + +function assert(condition, message) { + if (!condition) { + fail(message); + } +} + +function relative(file) { + return path.relative(ROOT, file); +} + +function readJson(file) { + try { + return JSON.parse(fs.readFileSync(file, "utf8")); + } catch (error) { + fail(`Cannot parse JSON at ${relative(file)}: ${error.message}`); + } +} + +function hasOwn(object, field) { + return Object.prototype.hasOwnProperty.call(object, field); +} + +assert(fs.existsSync(MANIFEST_PATH), "Vendored manifest is missing."); +assert(fs.existsSync(CHECKSUM_PATH), "Vendored manifest checksum is missing."); + +const manifestBytes = fs.readFileSync(MANIFEST_PATH); +const manifest = readJson(MANIFEST_PATH); +const checksumLine = fs.readFileSync(CHECKSUM_PATH, "utf8").trim(); +const expectedHash = checksumLine.split(/\s+/)[0]; +const actualHash = crypto.createHash("sha256").update(manifestBytes).digest("hex"); + +assert(/^[0-9a-f]{64}$/.test(expectedHash), "Checksum file must start with a sha256 hash."); +assert(actualHash === expectedHash, "Vendored manifest checksum does not match."); + +for (const field of REQUIRED_FIELDS) { + assert(hasOwn(manifest, field), `Missing required field: ${field}`); +} + +assert(manifest.protocol_name === "MPLP", "protocol_name must be MPLP."); +assert(manifest.protocol_full_name === "Multi-Agent Lifecycle Protocol", "protocol_full_name mismatch."); +assert(/^[0-9]+\.[0-9]+\.[0-9]+$/.test(manifest.protocol_version), "protocol_version must be semver-like."); +assert(/^[0-9a-f]{40}$/.test(manifest.protocol_commit_sha), "protocol_commit_sha must be a 40-character sha."); +assert(manifest.copyright_holder === "Jearon Wong", "copyright_holder must be Jearon Wong."); +assert(manifest.license === "Apache-2.0", "license must remain Apache-2.0."); +assert( + manifest.canonical_repository === "https://github.com/Coregentis/MPLP-Protocol", + "canonical_repository must remain Coregentis/MPLP-Protocol." +); + +assert( + manifest.future_repository_target === "https://github.com/Multi-Agent-Lifecycle-Protocol/MPLP-Protocol", + "future_repository_target must remain the reserved future Protocol target." +); +assert(manifest.repo_migration_status?.status === "reserved_future_org_not_migrated", "repo migration status must remain deferred."); +assert(manifest.repo_migration_status?.current_org === "Coregentis", "current_org must remain Coregentis."); +assert(manifest.repo_migration_status?.future_org === "Multi-Agent-Lifecycle-Protocol", "future_org mismatch."); +assert(manifest.repo_migration_status?.migration_now === false, "repo migration must remain disabled."); +assert(manifest.repo_migration_status?.remotes_changed === false, "remotes_changed must be false."); +assert(manifest.repo_migration_status?.package_repository_urls_changed === false, "package_repository_urls_changed must be false."); +assert(manifest.package_replacement_status?.status === "planned_not_published", "package replacement must remain planned_not_published."); +assert(manifest.package_replacement_status?.package_actions_executed === false, "package actions must remain false."); + +const boundary = manifest.validation_lab_boundary || {}; +assert(boundary.non_certifying === true, "validation_lab_boundary.non_certifying must be true."); +assert(boundary.no_certification === true, "validation_lab_boundary.no_certification must be true."); +assert(boundary.no_endorsement === true, "validation_lab_boundary.no_endorsement must be true."); +assert(boundary.no_regulator_approval === true, "validation_lab_boundary.no_regulator_approval must be true."); +assert(boundary.no_protocol_truth_override === true, "validation_lab_boundary.no_protocol_truth_override must be true."); +assert(boundary.no_runtime_authority === true, "validation_lab_boundary.no_runtime_authority must be true."); +assert(boundary.no_sdk_authority === true, "validation_lab_boundary.no_sdk_authority must be true."); + +assert(Array.isArray(manifest.forbidden_claims), "forbidden_claims must be an array."); +const forbiddenClaims = manifest.forbidden_claims.join("\n"); +const forbiddenClaimsLower = forbiddenClaims.toLowerCase(); +assert(forbiddenClaimsLower.includes("certified"), "forbidden_claims must include certification boundary."); +assert(forbiddenClaimsLower.includes("endorsed"), "forbidden_claims must include endorsement boundary."); +assert(forbiddenClaimsLower.includes("regulator approved"), "forbidden_claims must include regulator approval boundary."); + +for (const claim of DIRECT_PROMOTIONAL_CLAIMS) { + assert( + !manifest.forbidden_claims.includes(claim), + `forbidden_claims must use boundary wording, not direct promotional phrase: ${claim}` + ); +} + +const notice = String(manifest.non_certification_notice || "").toLowerCase(); +assert(notice.includes("evidence-based"), "non_certification_notice must preserve evidence-based wording."); +assert(notice.includes("non-certifying"), "non_certification_notice must preserve non-certifying wording."); +assert(notice.includes("endorsement"), "non_certification_notice must preserve no-endorsement boundary."); +assert(notice.includes("regulator approval"), "non_certification_notice must preserve no-regulator-approval boundary."); +assert(notice.includes("runtime authority"), "non_certification_notice must preserve no-runtime-authority boundary."); +assert(notice.includes("sdk authority"), "non_certification_notice must preserve no-SDK-authority boundary."); +assert(notice.includes("protocol authority"), "non_certification_notice must preserve no-protocol-authority boundary."); + +console.log(`[protocol-manifest] checksum: ${actualHash}`); +console.log("[protocol-manifest] PASS: vendored manifest validated without network access."); +