From 0a1bac8977e9662fb0337250f6deadd2599f8eea Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 17:04:27 +0000 Subject: [PATCH 1/3] feat(spec)!: retire the plugin-security scan-result surface (#15932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-0049 enforce-or-remove; maintainer ruling 2026-09-07 (director seat, decision batch #65), adopted verbatim. The second half of #14919: that change retired `PluginSecurityScanner`, whose type-only import was the family's only importer of any kind, leaving `KernelSecurityScanResult`, `KernelSecurityVulnerability`, `PluginSecurityManifest.scanResults` and `PluginQualityMetrics.securityScan` fully published with zero consumers and no `.parse`/`.safeParse` site anywhere. The two defs leave the build whole (`RETIRED_DEFS_BY_MAJOR[18]`) because nothing parses them. The authorable keys are `retiredKey()` tombstones registered in `RETIRED_KEYS_BY_MAJOR[18]` — neither carrying shape is `.strict()`, so a bare deletion would strip an authored key in silence (ADR-0104). `PluginSecurityManifest.vulnerabilities` is a forced consequence: it was the last authorable referent of `KernelSecurityVulnerability`. No D2 conversion — a plugin security manifest and a plugin registry entry are package artifacts a publisher ships, never stack collection members and never stored `sys_metadata` rows. The D3 semantic entry `plugin-security-scan-result-surface-retired` carries the judgement. The marketplace `'scanning'` status and the incident `'malware'` type are deliberately untouched: the ruling made them conditional on a producer grep of `objectstack-ai/cloud`, which is not reachable from this session. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx --- ...in-security-scan-result-surface-retired.md | 87 +++++++ content/docs/references/index.mdx | 10 +- .../references/kernel/plugin-registry.mdx | 14 +- .../kernel/plugin-security-advanced.mdx | 71 +----- ...07-unknown-key-strictness-ledger.counts.md | 2 +- docs/qa/platform-checklist/FOLLOW-UPS.md | 20 +- packages/spec/api-surface/kernel.json | 6 - packages/spec/authorable-defaults/kernel.json | 2 - packages/spec/authorable-surface/kernel.json | 31 +-- packages/spec/declaration-map/kernel.json | 4 - packages/spec/export-origins/kernel.json | 6 - .../spec/json-schema.manifest/kernel.json | 2 - .../spec/src/kernel/plugin-registry.test.ts | 43 +--- .../spec/src/kernel/plugin-registry.zod.ts | 32 ++- .../kernel/plugin-security-advanced.zod.ts | 219 ++++-------------- ...in-security-scan-result-retirement.test.ts | 138 +++++++++++ .../18.kernel__KernelSecurityScanResult.ts | 27 +++ .../18.kernel__KernelSecurityVulnerability.ts | 28 +++ ...nel__PluginQualityMetrics__securityScan.ts | 27 +++ ...el__PluginSecurityManifest__scanResults.ts | 24 ++ ...PluginSecurityManifest__vulnerabilities.ts | 22 ++ ...in-security-scan-result-surface-retired.ts | 83 +++++++ packages/spec/src/migrations/registry.ts | 197 ++++++++++++++++ 23 files changed, 748 insertions(+), 347 deletions(-) create mode 100644 .changeset/15932-plugin-security-scan-result-surface-retired.md create mode 100644 packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityVulnerability.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginQualityMetrics__securityScan.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__vulnerabilities.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts diff --git a/.changeset/15932-plugin-security-scan-result-surface-retired.md b/.changeset/15932-plugin-security-scan-result-surface-retired.md new file mode 100644 index 00000000000..fc3533a7abe --- /dev/null +++ b/.changeset/15932-plugin-security-scan-result-surface-retired.md @@ -0,0 +1,87 @@ +--- +'@objectstack/spec': minor +--- + +feat(spec)!: retire the plugin-security scan-result surface — zero consumers after #14919 (#15932) + +**BREAKING** — the plugin-security scan-result family is removed. ADR-0049 +enforce-or-remove; maintainer ruling 2026-09-07 (director seat, decision batch +#65), adopted verbatim 「同意」. + +This is the second half of #14919. That change retired `PluginSecurityScanner`, +the `@objectstack/core` class that shipped as a security control and returned +`status: "passed"` for every plugin it was ever handed. The **schemas** it fed +survived it — and that scanner's type-only import was their only importer of any +kind, so the family went from one type-only importer to **zero consumers** while +staying fully published: 27 authorable rows on `authorable-surface/kernel.json`, +six `api-surface` exports, two authorable defaults and two json-schema manifest +keys, with no `.parse` or `.safeParse` site against either schema anywhere. An +author could write any of it, be accepted, and get nothing. That is the +declared-not-enforced shape, one layer out from the class removed for the same +reason. "Declare an owner to enforce" was refused by name: it would rebuild the +scanner just retired. + +### FROM → TO + +| removed | what to write instead | +| --- | --- | +| `KernelSecurityScanResult`, `KernelSecurityScanResultParsed`, `KernelSecurityScanResultSchema` (exports) | nothing — delete the import. No replacement type exists. | +| `KernelSecurityVulnerability`, `KernelSecurityVulnerabilityParsed`, `KernelSecurityVulnerabilitySchema` (exports) | nothing — delete the import. No replacement type exists. | +| `PluginSecurityManifest.scanResults` | delete the key | +| `PluginSecurityManifest.vulnerabilities` | delete the key | +| `PluginQualityMetrics.securityScan` | delete the key | + +**The one-line fix: delete the keys and every import of the two types.** Plugin +security scanning is not a platform capability and there is no replacement +schema. What the platform does still enforce is unchanged: `permissions` and +`sandbox` on the same `PluginSecurityManifest`, and artifact provenance through +`verifyPluginArtifactIntegrity` and the plugin signature verifier — which tell +you an artifact is the one its publisher signed, and never that it is safe. For +dependency vulnerabilities use the tools built for it against your own project +(`npm audit` / `pnpm audit`, Dependabot, the GitHub Advisory Database, OSV), and +treat an unaudited third-party plugin as untrusted code. A publisher who used +`scanResults` to advertise diligence keeps the surviving `securityContact` and +`vulnerabilityDisclosure` blocks, which are contact terms rather than a verdict. + +⚠️ Runtime behaviour is deliberately **unchanged**. Nothing ever read any of +these keys, so deleting one removes no check that was running. A consumer that +gated on `securityScan.passed === true` was gating on nothing — the remediation +is to audit with a real tool, not to find a replacement key. + +### The retirement kit + +- The two **defs** leave the build whole — `RETIRED_DEFS_BY_MAJOR[18]` + (`kernel/KernelSecurityScanResult`, `kernel/KernelSecurityVulnerability`) — + because nothing parses them, so there is no author a tombstone could reach. +- The three **authorable keys** are `retiredKey()` tombstones registered in + `RETIRED_KEYS_BY_MAJOR[18]`. Neither carrying shape is `.strict()`, so a bare + deletion would strip an authored key in silence (ADR-0104): the tombstone is + audible in both channels — `tsc` (input type `never`) and the parse, which + raises the prescription itself. +- **No D2 conversion.** A plugin security manifest and a plugin registry entry + are package artifacts a publisher ships, never stack collection members and + never stored `sys_metadata` rows, so the chain has no seam that would see one + — the disposition the sibling `kernel-plugin-security-durations-unit-in-key` + entry already records for this same manifest. The D3 semantic entry + `plugin-security-scan-result-surface-retired` carries the judgement. +- `PluginSecurityManifest.vulnerabilities` is a **forced consequence**, not one + of the four names the ruling listed: it was the last authorable referent of + `KernelSecurityVulnerability` and could not outlive the def. +- **No deprecation window** (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」). + +⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` +is published, so this is breaking for consumers no download, dependent or source +telemetry was consulted for — exactly as #14919's changeset says of its own three +exports. That was an input to the ruling, not a reason to soften the removal. + +⛔ **Untouched, and not checked:** the marketplace `'scanning'` status and the +incident `'malware'` type. The ruling made them conditional on a producer grep of +`objectstack-ai/cloud`, and that repository was not reachable from the session +that executed this card. + +`Clause-②: yes (narrowing)` — a published surface is removed: six exports leave +the built `.d.ts` and three authorable keys stop being writable, so the accept +set a consumer writes against narrows. Nothing is widened and nothing is +renamed. Contract-review tier. + + diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index a7a6966df63..47d11b4cc71 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1533 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1531 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -25,7 +25,7 @@ counts are sums of the rows they head. Regenerate with | [Data Protocol](/docs/references/data) | 29 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | | [Integration Protocol](/docs/references/integration) | 1 | 24 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. | -| [Kernel Protocol](/docs/references/kernel) | 30 | 159 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. | +| [Kernel Protocol](/docs/references/kernel) | 30 | 157 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. | | [Marketplace Protocol](/docs/references/marketplace) | 4 | 30 | The package & marketplace format — package identity and versions, listing, publish, review, search, install, template manifests. | | [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. | | [Security Protocol](/docs/references/security) | 5 | 30 | Permission sets, row-level security, sharing rules, tenancy posture. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 158 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **195** | **1533** | 14 protocol modules | +| **Total** | **195** | **1531** | 14 protocol modules | --- @@ -197,7 +197,7 @@ The single connector protocol (ADR-0097) — catalog descriptors and provider-bo ## Kernel Protocol -**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **30 pages, 159 schemas** +**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **30 pages, 157 schemas** Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. @@ -227,7 +227,7 @@ Plugin lifecycle and manifests, capabilities and security, metadata loading, ser | [`plugin-loading.zod.ts`](/docs/references/kernel/plugin-loading) | `PluginLoadingEvent`, `PluginLoadingState` | | [`plugin-registry.zod.ts`](/docs/references/kernel/plugin-registry) | `PluginInstallConfig`, `PluginQualityMetrics`, `PluginRegistryEntry`, `PluginSearchFilters`, `PluginStatistics`, `PluginVendor` | | [`plugin-security.zod.ts`](/docs/references/kernel/plugin-security) | `DependencyGraph`, `DependencyGraphNode`, `PackageDependencyConflict`, `PackageDependencyResolutionResult`, `PluginProvenance`, `PluginTrustScore`, `ResolvedPackageDependency`, `SBOM`, `SBOMEntry`, `SecurityPolicy`, `SecurityScanResult`, `SecurityVulnerability`, `VulnerabilitySeverity` | -| [`plugin-security-advanced.zod.ts`](/docs/references/kernel/plugin-security-advanced) | `KernelSecurityPolicy`, `KernelSecurityScanResult`, `KernelSecurityVulnerability`, `PermissionAction`, `PermissionScope`, `PluginPermission`, `PluginPermissionSet`, `PluginSecurityManifest`, `PluginTrustLevel`, `ResourceType`, `RuntimeConfig`, `SandboxConfig` | +| [`plugin-security-advanced.zod.ts`](/docs/references/kernel/plugin-security-advanced) | `KernelSecurityPolicy`, `PermissionAction`, `PermissionScope`, `PluginPermission`, `PluginPermissionSet`, `PluginSecurityManifest`, `PluginTrustLevel`, `ResourceType`, `RuntimeConfig`, `SandboxConfig` | | [`plugin-structure.zod.ts`](/docs/references/kernel/plugin-structure) | `OpsDomainModule`, `OpsFilePath`, `OpsPluginStructure` | | [`plugin-validator.zod.ts`](/docs/references/kernel/plugin-validator) | `PluginMetadata`, `ValidationError`, `ValidationResult`, `ValidationWarning` | | [`plugin-versioning.zod.ts`](/docs/references/kernel/plugin-versioning) | `BreakingChange`, `CompatibilityLevel`, `CompatibilityMatrixEntry`, `DependencyConflict`, `DeprecationNotice`, `MultiVersionSupport`, `PluginCompatibilityMatrix`, `PluginDependencyResolutionResult`, `PluginVersionMetadata`, `SemanticVersion`, `VersionConstraint` | diff --git a/content/docs/references/kernel/plugin-registry.mdx b/content/docs/references/kernel/plugin-registry.mdx index ff0125e8b62..04b9295cc2e 100644 --- a/content/docs/references/kernel/plugin-registry.mdx +++ b/content/docs/references/kernel/plugin-registry.mdx @@ -51,7 +51,7 @@ const result = PluginInstallConfigSchema.parse(data); | **testCoverage** | `number` | optional | | | **documentationScore** | `number` | optional | | | **codeQuality** | `number` | optional | | -| **securityScan** | `{ lastScanDate?: string; vulnerabilities?: object; passed: boolean }` | optional | | +| **securityScan** | `never` | optional | [REMOVED] `PluginQualityMetrics.securityScan` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the block declared a last-scan date, per-severity vulnerability counts and a `passed` verdict, and no scanner, registry, installer or UI ever produced or read one, so a plugin could publish `passed: true` with nothing at all behind it. Delete the key. There is no replacement key: plugin security scanning is not a platform capability, and the scan-result family this block mirrored was retired with it. The sibling quality metrics — `testCoverage`, `documentationScore`, `codeQuality` and `conformanceTests` — are unchanged. Audit dependencies with a tool built for it (npm audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV). | | **conformanceTests** | `{ protocolId: string; passed: boolean; totalTests: integer; passedTests: integer; … }[]` | optional | | ### Nested Shape: `PluginQualityMetrics.conformanceTests[number]` @@ -85,7 +85,7 @@ const result = PluginInstallConfigSchema.parse(data); | **compatibility** | `{ minObjectStackVersion?: string; maxObjectStackVersion?: string; nodeVersion?: string; platforms?: Enum<'linux' \| 'darwin' \| 'win32' \| 'browser'>[] }` | optional | | | **links** | `{ homepage?: string; repository?: string; documentation?: string; bugs?: string; … }` | optional | | | **media** | `{ icon?: string; logo?: string; screenshots?: string[]; video?: string }` | optional | | -| **quality** | `{ testCoverage?: number; documentationScore?: number; codeQuality?: number; securityScan?: object; … }` | optional | | +| **quality** | `{ testCoverage?: number; documentationScore?: number; codeQuality?: number; conformanceTests?: object[] }` | optional | | | **statistics** | `{ downloads: integer; downloadsLastMonth: integer; activeInstallations: integer; ratings?: object; … }` | optional | | | **license** | `string` | optional | SPDX license identifier | | **pricing** | `{ model: Enum<'free' \| 'freemium' \| 'paid' \| 'enterprise'>; price?: number; currency?: string; billingPeriod?: Enum<'one-time' \| 'monthly' \| 'yearly'> }` | optional | | @@ -117,6 +117,16 @@ const result = PluginInstallConfigSchema.parse(data); | **extensionPoints** | `{ id: string; name: string; description?: string; type: Enum<'action' \| 'hook' \| 'widget' \| 'provider' \| 'transformer' \| 'validator' \| 'decorator'>; … }[]` | optional | Points where other plugins can extend this plugin | | **extensions** | `{ targetPluginId: string; extensionPointId: string; implementation: string; priority: integer }[]` | optional | Extensions contributed to other plugins | +### Nested Shape: `PluginRegistryEntry.quality` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **testCoverage** | `number` | optional | | +| **documentationScore** | `number` | optional | | +| **codeQuality** | `number` | optional | | +| **securityScan** | `never` | optional | [REMOVED] `PluginQualityMetrics.securityScan` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the block declared a last-scan date, per-severity vulnerability counts and a `passed` verdict, and no scanner, registry, installer or UI ever produced or read one, so a plugin could publish `passed: true` with nothing at all behind it. Delete the key. There is no replacement key: plugin security scanning is not a platform capability, and the scan-result family this block mirrored was retired with it. The sibling quality metrics — `testCoverage`, `documentationScore`, `codeQuality` and `conformanceTests` — are unchanged. Audit dependencies with a tool built for it (npm audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV). | +| **conformanceTests** | `{ protocolId: string; passed: boolean; totalTests: integer; passedTests: integer; … }[]` | optional | | + --- diff --git a/content/docs/references/kernel/plugin-security-advanced.mdx b/content/docs/references/kernel/plugin-security-advanced.mdx index a2818598dce..a492afe4dc4 100644 --- a/content/docs/references/kernel/plugin-security-advanced.mdx +++ b/content/docs/references/kernel/plugin-security-advanced.mdx @@ -14,9 +14,14 @@ Features: - Fine-grained permission system - Resource access control - Sandboxing and isolation -- Security scanning and verification - Runtime security monitoring +⛔ NOT security scanning. The scan-result family — `KernelSecurityScanResult`, +`KernelSecurityVulnerability`, `PluginSecurityManifest.scanResults` and its +sibling list `.vulnerabilities` — was retired under ADR-0049 enforce-or-remove +(#15932), after the runtime scanner that was its last type-only importer went +the same way (#14919). Nothing on this platform scans a plugin. + **Source:** `packages/spec/src/kernel/plugin-security-advanced.zod.ts` @@ -24,8 +29,8 @@ Features: ## TypeScript Usage ```typescript -import { KernelSecurityPolicySchema, KernelSecurityScanResultSchema, KernelSecurityVulnerabilitySchema, PermissionActionSchema, PermissionScopeSchema, PluginPermissionSchema, PluginPermissionSetSchema, PluginSecurityManifestSchema, PluginTrustLevelSchema, ResourceTypeSchema, RuntimeConfigSchema, SandboxConfigSchema } from '@objectstack/spec/kernel'; -import type { KernelSecurityPolicy, KernelSecurityScanResult, KernelSecurityVulnerability, PermissionAction, PermissionScope, PluginPermission, PluginPermissionSet, PluginSecurityManifest, PluginTrustLevel, ResourceType, RuntimeConfig, SandboxConfig } from '@objectstack/spec/kernel'; +import { KernelSecurityPolicySchema, PermissionActionSchema, PermissionScopeSchema, PluginPermissionSchema, PluginPermissionSetSchema, PluginSecurityManifestSchema, PluginTrustLevelSchema, ResourceTypeSchema, RuntimeConfigSchema, SandboxConfigSchema } from '@objectstack/spec/kernel'; +import type { KernelSecurityPolicy, PermissionAction, PermissionScope, PluginPermission, PluginPermissionSet, PluginSecurityManifest, PluginTrustLevel, ResourceType, RuntimeConfig, SandboxConfig } from '@objectstack/spec/kernel'; // Validate data const result = KernelSecurityPolicySchema.parse(data); @@ -93,62 +98,6 @@ const result = KernelSecurityPolicySchema.parse(data); | **retention** | `never` | optional | [REMOVED] `KernelSecurityPolicy.auditLog.retention` was renamed to `retentionDays` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `retentionDays`; the value (days) is unchanged. | ---- - -## KernelSecurityScanResult - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timestamp** | `string` | ✅ | | -| **scanner** | `{ name: string; version: string }` | ✅ | | -| **status** | `Enum<'passed' \| 'failed' \| 'warning'>` | ✅ | | -| **vulnerabilities** | `{ cve?: string; id: string; severity: Enum<'critical' \| 'high' \| 'medium' \| 'low' \| 'info'>; category?: string; … }[]` | optional | | -| **codeIssues** | `{ severity: Enum<'error' \| 'warning' \| 'info'>; type: string; file: string; line?: integer; … }[]` | optional | | -| **dependencyVulnerabilities** | `{ package: string; version: string; vulnerability: object }[]` | optional | | -| **licenseCompliance** | `{ status: Enum<'compliant' \| 'non-compliant' \| 'unknown'>; issues?: object[] }` | optional | | -| **summary** | `{ totalVulnerabilities: integer; criticalCount: integer; highCount: integer; mediumCount: integer; … }` | ✅ | | - -### Nested Shape: `KernelSecurityScanResult.codeIssues[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **severity** | `Enum<'error' \| 'warning' \| 'info'>` | ✅ | | -| **type** | `string` | ✅ | Issue type (e.g., sql-injection, xss) | -| **file** | `string` | ✅ | | -| **line** | `integer` | optional | | -| **message** | `string` | ✅ | | -| **suggestion** | `string` | optional | | - - ---- - -## KernelSecurityVulnerability - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **cve** | `string` | optional | | -| **id** | `string` | ✅ | | -| **severity** | `Enum<'critical' \| 'high' \| 'medium' \| 'low' \| 'info'>` | ✅ | | -| **category** | `string` | optional | | -| **title** | `string` | ✅ | | -| **location** | `string` | optional | | -| **remediation** | `string` | optional | | -| **description** | `string` | ✅ | | -| **affectedVersions** | `string[]` | ✅ | | -| **fixedIn** | `string[]` | optional | | -| **cvssScore** | `number` | optional | | -| **exploitAvailable** | `boolean` | optional (default: `false`) | | -| **patchAvailable** | `boolean` | optional (default: `false`) | | -| **workaround** | `string` | optional | | -| **references** | `string[]` | optional | | -| **discoveredDate** | `string` | optional | | -| **publishedDate** | `string` | optional | | - - --- ## PermissionAction @@ -277,8 +226,8 @@ Scope of permission application | **permissions** | `{ permissions: object[]; groups?: object[]; defaultGrant?: Enum<'prompt' \| 'allow' \| 'deny' \| 'inherit'> }` | ✅ | | | **sandbox** | `{ enabled?: boolean; level?: Enum<'none' \| 'minimal' \| 'standard' \| 'strict' \| 'paranoid'>; runtime?: object; filesystem?: object; … }` | ✅ | | | **policy** | `{ csp?: object; cors?: object; rateLimit?: object; authentication?: object; … }` | optional | | -| **scanResults** | `{ timestamp: string; scanner: object; status: Enum<'passed' \| 'failed' \| 'warning'>; vulnerabilities?: object[]; … }[]` | optional | | -| **vulnerabilities** | `{ cve?: string; id: string; severity: Enum<'critical' \| 'high' \| 'medium' \| 'low' \| 'info'>; category?: string; … }[]` | optional | | +| **scanResults** | `never` | optional | [REMOVED] `PluginSecurityManifest.scanResults` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing on this platform ever produced, stored or read a security scan result, so an authored array parsed cleanly and changed nothing. Delete the key. There is no replacement key: plugin security scanning is not a platform capability. What this manifest still enforces is `permissions` and `sandbox`; artifact provenance is answered by the plugin signature verifier, which tells you an artifact is the one its publisher signed and never that it is safe. Audit dependencies with a tool built for it (npm audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV). | +| **vulnerabilities** | `never` | optional | [REMOVED] `PluginSecurityManifest.vulnerabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it was an array of `KernelSecurityVulnerability`, the other half of the scan-result family, and leaves with it: nothing ever wrote the list and nothing ever read it, so declaring a known vulnerability against a plugin warned nobody and blocked no install. Delete the key. There is no replacement key: plugin security scanning is not a platform capability. Publish vulnerability contact and disclosure terms through the surviving `securityContact` and `vulnerabilityDisclosure` blocks on this same manifest, and audit dependencies with a tool built for it (npm audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV). | | **codeSigning** | `{ signed: boolean; signature?: string; certificate?: string; algorithm?: string; … }` | optional | | | **certifications** | `{ name: string; issuer: string; issuedDate: string; expiryDate?: string; … }[]` | optional | | | **securityContact** | `{ email?: string; url?: string; pgpKey?: string }` | optional | | diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index f00b4554d0b..f1e5d637489 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -260,7 +260,7 @@ directory rather than per file. | `api/` | 451 | | `identity/` | 32 | | `integration/` | 8 | -| `kernel/` | 257 | +| `kernel/` | 247 | | `marketplace/` | 29 | | `qa/` | 6 | | `shared/` | 20 | diff --git a/docs/qa/platform-checklist/FOLLOW-UPS.md b/docs/qa/platform-checklist/FOLLOW-UPS.md index 42b08ba8f63..10652b66738 100644 --- a/docs/qa/platform-checklist/FOLLOW-UPS.md +++ b/docs/qa/platform-checklist/FOLLOW-UPS.md @@ -183,12 +183,26 @@ governance hole. | surface | evidence | the deadness, precisely | |---|---|---| -| `KernelSecurityScanResult` / `KernelSecurityVulnerability` / `PluginSecurityManifest.scanResults` (`packages/spec/src/kernel/plugin-security-advanced.zod.ts,476,625`) | no `.parse`/`.safeParse` site anywhere; **zero** consumers of any kind since #14919 retired the dead scanner that was the last type-only importer | 22 rows published to `packages/spec/authorable-surface/kernel.json` with zero authors and zero parsers. The whole `plugin-security-advanced` module has no runtime consumer. | -| `PluginQualityMetrics.securityScan` (`packages/spec/src/kernel/plugin-registry.zod.ts`) | spec self-test only | Nothing reads or writes it at runtime. | +| ~~`KernelSecurityScanResult` / `KernelSecurityVulnerability` / `PluginSecurityManifest.scanResults`~~ (`packages/spec/src/kernel/plugin-security-advanced.zod.ts`) | no `.parse`/`.safeParse` site anywhere; **zero** consumers of any kind since #14919 retired the dead scanner that was the last type-only importer | **CLOSED by removal, #15932** (ruling below). The count this row carried was **22**; the retirement measured **27** authorable rows for the named surface — 8 + 17 for the two defs, plus one each for `PluginSecurityManifest.scanResults` and `PluginQualityMetrics.securityScan`. The 22 is not reconciled, only superseded. | +| ~~`PluginQualityMetrics.securityScan`~~ (`packages/spec/src/kernel/plugin-registry.zod.ts`) | spec self-test only | **CLOSED by removal, #15932** — tombstoned with the family it mirrored. | | Marketplace/incident scan vocab (`marketplace.zod.ts` 'scanning' status, `marketplace-admin.zod.ts,193`, `incident-response.zod.ts` 'malware') | declared-only enum members, no producer in this repo | Cloud/EE surface. Same shape as the `'failed'`/`'expired'` upload statuses #7667 had to close: declared, published, no writer. | | MetadataPlugin FS scan + `metadata-fs` boot scan (`packages/metadata/src/plugin.ts,270` — `watch ?? false`; `packages/runtime/src/standalone-stack.ts` hard-off; `metadata-fs` unwired from any `os dev`/`os serve` lane) | unit-pinned in-package only | No reachable fixture from any shipped boot; if a future lane wires `metadata-fs`, the boot-scan/watcher dot-entry divergence is the risk to test first. | -The scanner row above was **CLOSED by removal** in #14919 (maintainer ruling, +**The first two rows above were CLOSED by removal in #15932** (maintainer ruling, +director seat, decision batch #65, 2026-09-07, adopted verbatim 「同意」): the two defs +left the build whole (`RETIRED_DEFS_BY_MAJOR[18]`), and the authorable carriers — +`PluginSecurityManifest.scanResults`, its sibling list `.vulnerabilities` and +`PluginQualityMetrics.securityScan` — are `retiredKey()` tombstones registered in +`RETIRED_KEYS_BY_MAJOR[18]`. "Declare an owner to enforce" was refused by name: it would +rebuild the scanner #14919 had just retired for the same reason. ⛔ Do not re-derive it. + +⚠️ **The third row is NOT closed and was NOT checked.** The ruling made the marketplace +`'scanning'` status and the incident `'malware'` type conditional on a producer grep of +`objectstack-ai/cloud`, and that repository was not reachable from the session that +executed #15932 — so those members are untouched, and their absence from a diff is not +evidence about them. The ready-to-run command is on #15932. + +The scanner row further down was **CLOSED by removal** in #14919 (maintainer ruling, director summon #14, decision batch #42): the class, its barrel export, its `packages/core/examples/` demonstration and the `PHASE2_IMPLEMENTATION.md` section that advertised it are gone, and that section now states plainly that plugin security scanning diff --git a/packages/spec/api-surface/kernel.json b/packages/spec/api-surface/kernel.json index d298d04266f..951abcd5f5a 100644 --- a/packages/spec/api-surface/kernel.json +++ b/packages/spec/api-surface/kernel.json @@ -142,12 +142,6 @@ "KernelSecurityPolicy (type)", "KernelSecurityPolicyParsed (type)", "KernelSecurityPolicySchema (const)", - "KernelSecurityScanResult (type)", - "KernelSecurityScanResultParsed (type)", - "KernelSecurityScanResultSchema (const)", - "KernelSecurityVulnerability (type)", - "KernelSecurityVulnerabilityParsed (type)", - "KernelSecurityVulnerabilitySchema (const)", "LintableAuthoringCollection (interface)", "ListPackagesRequest (type)", "ListPackagesRequestSchema (const)", diff --git a/packages/spec/authorable-defaults/kernel.json b/packages/spec/authorable-defaults/kernel.json index 1f7d76063f4..e4997e515e6 100644 --- a/packages/spec/authorable-defaults/kernel.json +++ b/packages/spec/authorable-defaults/kernel.json @@ -51,8 +51,6 @@ "kernel/InstalledPackage:status = \"installed\"", "kernel/KernelContext:features = {}", "kernel/KernelContext:mode = \"production\"", - "kernel/KernelSecurityVulnerability:exploitAvailable = false", - "kernel/KernelSecurityVulnerability:patchAvailable = false", "kernel/Manifest:defaultDatasource = \"default\"", "kernel/Manifest:scope = \"project\"", "kernel/MetadataDiffItem:hasConflict = false", diff --git a/packages/spec/authorable-surface/kernel.json b/packages/spec/authorable-surface/kernel.json index 7b06132c37e..b2e931f40eb 100644 --- a/packages/spec/authorable-surface/kernel.json +++ b/packages/spec/authorable-surface/kernel.json @@ -251,31 +251,6 @@ "kernel/KernelSecurityPolicy:csp", "kernel/KernelSecurityPolicy:encryption", "kernel/KernelSecurityPolicy:rateLimit", - "kernel/KernelSecurityScanResult:codeIssues", - "kernel/KernelSecurityScanResult:dependencyVulnerabilities", - "kernel/KernelSecurityScanResult:licenseCompliance", - "kernel/KernelSecurityScanResult:scanner", - "kernel/KernelSecurityScanResult:status", - "kernel/KernelSecurityScanResult:summary", - "kernel/KernelSecurityScanResult:timestamp", - "kernel/KernelSecurityScanResult:vulnerabilities", - "kernel/KernelSecurityVulnerability:affectedVersions", - "kernel/KernelSecurityVulnerability:category", - "kernel/KernelSecurityVulnerability:cve", - "kernel/KernelSecurityVulnerability:cvssScore", - "kernel/KernelSecurityVulnerability:description", - "kernel/KernelSecurityVulnerability:discoveredDate", - "kernel/KernelSecurityVulnerability:exploitAvailable", - "kernel/KernelSecurityVulnerability:fixedIn", - "kernel/KernelSecurityVulnerability:id", - "kernel/KernelSecurityVulnerability:location", - "kernel/KernelSecurityVulnerability:patchAvailable", - "kernel/KernelSecurityVulnerability:publishedDate", - "kernel/KernelSecurityVulnerability:references", - "kernel/KernelSecurityVulnerability:remediation", - "kernel/KernelSecurityVulnerability:severity", - "kernel/KernelSecurityVulnerability:title", - "kernel/KernelSecurityVulnerability:workaround", "kernel/ListPackagesRequest:enabled", "kernel/ListPackagesRequest:status", "kernel/ListPackagesRequest:type", @@ -533,7 +508,7 @@ "kernel/PluginQualityMetrics:codeQuality", "kernel/PluginQualityMetrics:conformanceTests", "kernel/PluginQualityMetrics:documentationScore", - "kernel/PluginQualityMetrics:securityScan", + "kernel/PluginQualityMetrics:securityScan [RETIRED]", "kernel/PluginQualityMetrics:testCoverage", "kernel/PluginRegistryEntry:capabilities", "kernel/PluginRegistryEntry:category", @@ -574,10 +549,10 @@ "kernel/PluginSecurityManifest:pluginId", "kernel/PluginSecurityManifest:policy", "kernel/PluginSecurityManifest:sandbox", - "kernel/PluginSecurityManifest:scanResults", + "kernel/PluginSecurityManifest:scanResults [RETIRED]", "kernel/PluginSecurityManifest:securityContact", "kernel/PluginSecurityManifest:trustLevel", - "kernel/PluginSecurityManifest:vulnerabilities", + "kernel/PluginSecurityManifest:vulnerabilities [RETIRED]", "kernel/PluginSecurityManifest:vulnerabilityDisclosure", "kernel/PluginStartupResult:duration [RETIRED]", "kernel/PluginStartupResult:durationMs", diff --git a/packages/spec/declaration-map/kernel.json b/packages/spec/declaration-map/kernel.json index 10ff42c6b22..92f5db85d9f 100644 --- a/packages/spec/declaration-map/kernel.json +++ b/packages/spec/declaration-map/kernel.json @@ -95,10 +95,6 @@ "KernelContextSchema": "kernel/KernelContext", "KernelSecurityPolicy": "kernel/KernelSecurityPolicy", "KernelSecurityPolicySchema": "kernel/KernelSecurityPolicy", - "KernelSecurityScanResult": "kernel/KernelSecurityScanResult", - "KernelSecurityScanResultSchema": "kernel/KernelSecurityScanResult", - "KernelSecurityVulnerability": "kernel/KernelSecurityVulnerability", - "KernelSecurityVulnerabilitySchema": "kernel/KernelSecurityVulnerability", "ListPackagesRequest": "kernel/ListPackagesRequest", "ListPackagesRequestSchema": "kernel/ListPackagesRequest", "ListPackagesResponse": "kernel/ListPackagesResponse", diff --git a/packages/spec/export-origins/kernel.json b/packages/spec/export-origins/kernel.json index c66b7b3da0f..7ba8764d433 100644 --- a/packages/spec/export-origins/kernel.json +++ b/packages/spec/export-origins/kernel.json @@ -141,12 +141,6 @@ "KernelSecurityPolicy": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityPolicy (type)", "KernelSecurityPolicyParsed": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityPolicyParsed (type)", "KernelSecurityPolicySchema": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityPolicySchema (const)", - "KernelSecurityScanResult": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityScanResult (type)", - "KernelSecurityScanResultParsed": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityScanResultParsed (type)", - "KernelSecurityScanResultSchema": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityScanResultSchema (const)", - "KernelSecurityVulnerability": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityVulnerability (type)", - "KernelSecurityVulnerabilityParsed": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityVulnerabilityParsed (type)", - "KernelSecurityVulnerabilitySchema": "src/kernel/plugin-security-advanced.zod.ts#KernelSecurityVulnerabilitySchema (const)", "LintableAuthoringCollection": "src/kernel/metadata-authoring-lint.ts#LintableAuthoringCollection (interface)", "ListPackagesRequest": "src/kernel/package-registry.zod.ts#ListPackagesRequest (type)", "ListPackagesRequestSchema": "src/kernel/package-registry.zod.ts#ListPackagesRequestSchema (const)", diff --git a/packages/spec/json-schema.manifest/kernel.json b/packages/spec/json-schema.manifest/kernel.json index 4fc83d18c37..93d80ccb801 100644 --- a/packages/spec/json-schema.manifest/kernel.json +++ b/packages/spec/json-schema.manifest/kernel.json @@ -50,8 +50,6 @@ "kernel/InstalledPackage", "kernel/KernelContext", "kernel/KernelSecurityPolicy", - "kernel/KernelSecurityScanResult", - "kernel/KernelSecurityVulnerability", "kernel/ListPackagesRequest", "kernel/ListPackagesResponse", "kernel/Manifest", diff --git a/packages/spec/src/kernel/plugin-registry.test.ts b/packages/spec/src/kernel/plugin-registry.test.ts index 9b419f10e5b..34a433b6556 100644 --- a/packages/spec/src/kernel/plugin-registry.test.ts +++ b/packages/spec/src/kernel/plugin-registry.test.ts @@ -111,43 +111,12 @@ describe('Plugin Registry Schemas', () => { expect(() => PluginQualityMetricsSchema.parse({ codeQuality: -5 })).toThrow(); }); - it('should accept valid security scan', () => { - const metrics = PluginQualityMetricsSchema.parse({ - securityScan: { - lastScanDate: '2024-01-15T10:00:00Z', - vulnerabilities: { - critical: 0, - high: 1, - medium: 2, - low: 5, - }, - passed: true, - }, - }); - expect(metrics.securityScan?.passed).toBe(true); - expect(metrics.securityScan?.vulnerabilities?.high).toBe(1); - }); - - it('should apply defaults in security scan vulnerabilities', () => { - const metrics = PluginQualityMetricsSchema.parse({ - securityScan: { - vulnerabilities: {}, - }, - }); - expect(metrics.securityScan?.vulnerabilities?.critical).toBe(0); - expect(metrics.securityScan?.vulnerabilities?.high).toBe(0); - expect(metrics.securityScan?.vulnerabilities?.medium).toBe(0); - expect(metrics.securityScan?.vulnerabilities?.low).toBe(0); - expect(metrics.securityScan?.passed).toBe(false); - }); - - it('should reject negative vulnerability counts', () => { - expect(() => PluginQualityMetricsSchema.parse({ - securityScan: { - vulnerabilities: { critical: -1, high: 0, medium: 0, low: 0 }, - }, - })).toThrow(); - }); + // The three `securityScan` cases that stood here left with the key + // (#15932, ADR-0049 enforce-or-remove). Their replacement is not a + // narrower version of them: it is the refusal, pinned in + // `plugin-security-scan-result-retirement.test.ts` beside the rest of the + // family. ⛔ Do not re-add a "valid security scan" case — authoring the key + // is now a tsc error (input type `never`) and a parse error. it('should accept valid conformance tests', () => { const metrics = PluginQualityMetricsSchema.parse({ diff --git a/packages/spec/src/kernel/plugin-registry.zod.ts b/packages/spec/src/kernel/plugin-registry.zod.ts index 8517489c6b8..a2cfc0ba4d7 100644 --- a/packages/spec/src/kernel/plugin-registry.zod.ts +++ b/packages/spec/src/kernel/plugin-registry.zod.ts @@ -16,6 +16,19 @@ import { MAJOR_MINOR_PATCH_VERSION_PATTERN } from './version-grammar'; * Plugin Vendor Information */ import { lazySchema } from '../shared/lazy-schema'; +import { retiredKey } from '../shared/retired-key'; + +const SECURITY_SCAN_RETIRED = + '`PluginQualityMetrics.securityScan` was removed in @objectstack/spec 17 (ADR-0049 ' + + 'enforce-or-remove) — the block declared a last-scan date, per-severity vulnerability ' + + 'counts and a `passed` verdict, and no scanner, registry, installer or UI ever produced ' + + 'or read one, so a plugin could publish `passed: true` with nothing at all behind it. ' + + 'Delete the key. There is no replacement key: plugin security scanning is not a platform ' + + 'capability, and the scan-result family this block mirrored was retired with it. The ' + + 'sibling quality metrics — `testCoverage`, `documentationScore`, `codeQuality` and ' + + '`conformanceTests` — are unchanged. Audit dependencies with a tool built for it (npm ' + + 'audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV).'; + export const PluginVendorSchema = lazySchema(() => z.object({ /** * Vendor identifier (reverse domain notation) @@ -71,18 +84,15 @@ export const PluginQualityMetricsSchema = lazySchema(() => z.object({ codeQuality: z.number().min(0).max(100).optional(), /** - * Security scan status + * Tombstone: the plugin security-scan status is RETIRED (#15932, ADR-0049 + * enforce-or-remove), with the `plugin-security-advanced` scan-result family + * it mirrored. Not a bare deletion: `PluginQualityMetricsSchema` is not + * `.strict()`, so zod would strip an authored key in silence (ADR-0104). + * The key itself carried no default, so there is no materialized residue in + * previously built artifacts to accept (the inner defaults only ever fired + * for an author who wrote the block). */ - securityScan: z.object({ - lastScanDate: z.string().datetime().optional(), - vulnerabilities: z.object({ - critical: z.number().int().min(0).default(0), - high: z.number().int().min(0).default(0), - medium: z.number().int().min(0).default(0), - low: z.number().int().min(0).default(0), - }).optional(), - passed: z.boolean().default(false), - }).optional(), + securityScan: retiredKey(SECURITY_SCAN_RETIRED), /** * Conformance test results diff --git a/packages/spec/src/kernel/plugin-security-advanced.zod.ts b/packages/spec/src/kernel/plugin-security-advanced.zod.ts index 37b60343a7a..0f3af37f506 100644 --- a/packages/spec/src/kernel/plugin-security-advanced.zod.ts +++ b/packages/spec/src/kernel/plugin-security-advanced.zod.ts @@ -13,8 +13,13 @@ import { EvaluatedExpressionInputSchema } from '../shared/expression.zod'; * - Fine-grained permission system * - Resource access control * - Sandboxing and isolation - * - Security scanning and verification * - Runtime security monitoring + * + * ⛔ NOT security scanning. The scan-result family — `KernelSecurityScanResult`, + * `KernelSecurityVulnerability`, `PluginSecurityManifest.scanResults` and its + * sibling list `.vulnerabilities` — was retired under ADR-0049 enforce-or-remove + * (#15932), after the runtime scanner that was its last type-only importer went + * the same way (#14919). Nothing on this platform scans a plugin. */ /** @@ -346,6 +351,30 @@ const DISCLOSURE_RESPONSE_TIME_RETIRED = + 'name on `PluginHealthReport.metrics` was milliseconds — which is the confusion the rule ' + 'exists to remove. Rename the key to `responseTimeHours`; the value (hours) is unchanged.'; +// The scan-result family's two tombstones (#15932, ADR-0049 enforce-or-remove). +// Their value types — `KernelSecurityScanResult` and `KernelSecurityVulnerability` — +// are gone from this build entirely; see the header note above. +const SCAN_RESULTS_RETIRED = + '`PluginSecurityManifest.scanResults` was removed in @objectstack/spec 17 (ADR-0049 ' + + 'enforce-or-remove) — nothing on this platform ever produced, stored or read a security ' + + 'scan result, so an authored array parsed cleanly and changed nothing. Delete the key. ' + + 'There is no replacement key: plugin security scanning is not a platform capability. What ' + + 'this manifest still enforces is `permissions` and `sandbox`; artifact provenance is ' + + 'answered by the plugin signature verifier, which tells you an artifact is the one its ' + + 'publisher signed and never that it is safe. Audit dependencies with a tool built for it ' + + '(npm audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV).'; + +const MANIFEST_VULNERABILITIES_RETIRED = + '`PluginSecurityManifest.vulnerabilities` was removed in @objectstack/spec 17 (ADR-0049 ' + + 'enforce-or-remove) — it was an array of `KernelSecurityVulnerability`, the other half of ' + + 'the scan-result family, and leaves with it: nothing ever wrote the list and nothing ever ' + + 'read it, so declaring a known vulnerability against a plugin warned nobody and blocked ' + + 'no install. Delete the key. There is no replacement key: plugin security scanning is not ' + + 'a platform capability. Publish vulnerability contact and disclosure terms through the ' + + 'surviving `securityContact` and `vulnerabilityDisclosure` blocks on this same manifest, ' + + 'and audit dependencies with a tool built for it (npm audit, pnpm audit, Dependabot, the ' + + 'GitHub Advisory Database, OSV).'; + /** * Sandbox Configuration * Defines how plugin is isolated @@ -434,171 +463,6 @@ export const SandboxConfigSchema = lazySchema(() => z.object({ }).optional(), })); -/** - * Security Vulnerability - * Represents a known security vulnerability - */ -export const KernelSecurityVulnerabilitySchema = lazySchema(() => z.object({ - /** - * CVE identifier - */ - cve: z.string().optional(), - - /** - * Vulnerability identifier - */ - id: z.string(), - - /** - * Severity level - */ - severity: z.enum(['critical', 'high', 'medium', 'low', 'info']), - - /** - * Category (e.g., SAST, DAST, Dependency) - */ - category: z.string().optional(), - - /** - * Title - */ - title: z.string(), - - /** - * Location of the vulnerability - */ - location: z.string().optional(), - - /** - * Remediation steps - */ - remediation: z.string().optional(), - - /** - * Description - */ - description: z.string(), - - /** - * Affected versions - */ - affectedVersions: z.array(z.string()), - - /** - * Fixed in versions - */ - fixedIn: z.array(z.string()).optional(), - - /** - * CVSS score - */ - cvssScore: z.number().min(0).max(10).optional(), - - /** - * Exploit availability - */ - exploitAvailable: z.boolean().default(false), - - /** - * Patch available - */ - patchAvailable: z.boolean().default(false), - - /** - * Workaround - */ - workaround: z.string().optional(), - - /** - * References - */ - references: z.array(z.string()).optional(), - - /** - * Discovered date - */ - discoveredDate: z.string().datetime().optional(), - - /** - * Published date - */ - publishedDate: z.string().datetime().optional(), -})); - -/** - * Security Scan Result - * Result of security scanning - */ -export const KernelSecurityScanResultSchema = lazySchema(() => z.object({ - /** - * Scan timestamp - */ - timestamp: z.string().datetime(), - - /** - * Scanner information - */ - scanner: z.object({ - name: z.string(), - version: z.string(), - }), - - /** - * Overall status - */ - status: z.enum(['passed', 'failed', 'warning']), - - /** - * Vulnerabilities found - */ - vulnerabilities: z.array(KernelSecurityVulnerabilitySchema).optional(), - - /** - * Code quality issues - */ - codeIssues: z.array(z.object({ - severity: z.enum(['error', 'warning', 'info']), - type: z.string().describe('Issue type (e.g., sql-injection, xss)'), - file: z.string(), - line: z.number().int().optional(), - message: z.string(), - suggestion: z.string().optional(), - })).optional(), - - /** - * Dependency vulnerabilities - */ - dependencyVulnerabilities: z.array(z.object({ - package: z.string(), - version: z.string(), - vulnerability: KernelSecurityVulnerabilitySchema, - })).optional(), - - /** - * License compliance - */ - licenseCompliance: z.object({ - status: z.enum(['compliant', 'non-compliant', 'unknown']), - issues: z.array(z.object({ - package: z.string(), - license: z.string(), - reason: z.string(), - })).optional(), - }).optional(), - - /** - * Summary statistics - */ - summary: z.object({ - totalVulnerabilities: z.number().int(), - criticalCount: z.number().int(), - highCount: z.number().int(), - mediumCount: z.number().int(), - lowCount: z.number().int(), - infoCount: z.number().int(), - }), -})); - /** * Security Policy * Defines security policies for plugin @@ -721,14 +585,17 @@ export const PluginSecurityManifestSchema = lazySchema(() => z.object({ policy: KernelSecurityPolicySchema.optional(), /** - * Security scan results + * Tombstone: the scan-result surface is RETIRED (#15932, ADR-0049 + * enforce-or-remove). `KernelSecurityScanResult` and + * `KernelSecurityVulnerability` left this build with it — the last importer + * of either type went with `PluginSecurityScanner` (#14919). Not a bare + * deletion: this shape is not `.strict()`, so zod would strip an authored + * key in silence (ADR-0104). */ - scanResults: z.array(KernelSecurityScanResultSchema).optional(), - - /** - * Known vulnerabilities - */ - vulnerabilities: z.array(KernelSecurityVulnerabilitySchema).optional(), + scanResults: retiredKey(SCAN_RESULTS_RETIRED), + + /** Tombstone for the sibling list — same retirement, same reasoning. */ + vulnerabilities: retiredKey(MANIFEST_VULNERABILITIES_RETIRED), /** * Code signing @@ -793,12 +660,6 @@ export type RuntimeConfigParsed = z.infer; export type SandboxConfig = z.input; /** Post-parse shape of {@link SandboxConfig} — defaults applied, transforms run (ADR-0122). */ export type SandboxConfigParsed = z.infer; -export type KernelSecurityVulnerability = z.input; -/** Post-parse shape of {@link KernelSecurityVulnerability} — defaults applied, transforms run (ADR-0122). */ -export type KernelSecurityVulnerabilityParsed = z.infer; -export type KernelSecurityScanResult = z.input; -/** Post-parse shape of {@link KernelSecurityScanResult} — defaults applied, transforms run (ADR-0122). */ -export type KernelSecurityScanResultParsed = z.infer; export type KernelSecurityPolicy = z.input; /** Post-parse shape of {@link KernelSecurityPolicy} — defaults applied, transforms run (ADR-0122). */ export type KernelSecurityPolicyParsed = z.infer; diff --git a/packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts b/packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts new file mode 100644 index 00000000000..d1f29315de3 --- /dev/null +++ b/packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts @@ -0,0 +1,138 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect } from 'vitest'; + +import { PluginSecurityManifestSchema } from './plugin-security-advanced.zod'; +import { PluginQualityMetricsSchema } from './plugin-registry.zod'; + +// ─── [#15932] the plugin-security SCAN-RESULT family is REMOVED ─────────────── +// +// ADR-0049 enforce-or-remove; director seat, decision batch #65, 2026-09-07, +// maintainer verbatim 「同意」. Four members, one family: +// +// - `KernelSecurityScanResult` — whole def, out of the build +// - `KernelSecurityVulnerability` — whole def, out of the build +// - `PluginSecurityManifest.scanResults` — tombstoned key +// - `PluginQualityMetrics.securityScan` — tombstoned key +// +// plus `PluginSecurityManifest.vulnerabilities`, the last authorable referent of +// the second def and therefore a forced consequence of retiring it. +// +// This is the second half of #14919, which retired `PluginSecurityScanner` — the +// `@objectstack/core` class that shipped as a security control and returned +// `status: "passed"` for every plugin it was ever handed. That scanner was the +// family's ONLY importer of any kind (a type-only import), so its deletion moved +// these schemas from one type-only importer to zero consumers while they stayed +// fully published on the authorable surface. +// +// ⚠️ On the assertion set. A schema tombstone raises a `ZodError`, not the +// ADR-0112 envelope: its issues carry `code` and `path` and have no `status` +// field at all. So these pins assert the strongest set this surface really has — +// refusal, the issue `code`, the `path` naming WHICH key was refused, and the +// prescription text (where the wording is the contract, pin the wording). A bare +// `toThrow()` would stay green if the refusal moved to the wrong key or stopped +// carrying the fix. + +/** A manifest that is valid except for whatever an individual test adds. */ +const baseManifest = { + pluginId: 'com.example.plugin', + trustLevel: 'community', + permissions: { permissions: [] }, + sandbox: {}, +} as const; + +describe('[#15932] plugin-security scan-result retirement', () => { + it('REJECTS an authored `scanResults` array, naming the key and carrying the fix', () => { + const result = PluginSecurityManifestSchema.safeParse({ + ...baseManifest, + scanResults: [{ timestamp: '2026-01-15T10:00:00Z', status: 'passed' }], + }); + + expect(result.success).toBe(false); + if (result.success) return; // narrowing; the assertion above already failed + + const issue = result.error.issues.find((i) => i.path[0] === 'scanResults'); + expect(issue, 'the refusal must name `scanResults`').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(['scanResults']); + // The prescription IS the migration doc for whoever hits it — contract, not + // commentary. It must say the removal happened, and must NOT send the reader + // looking for a replacement key that does not exist. + expect(issue!.message).toMatch(/`PluginSecurityManifest\.scanResults`.*removed.*ADR-0049/s); + expect(issue!.message).toMatch(/Delete the key/s); + expect(issue!.message).toMatch(/no replacement key/s); + // An author who read a clean scan as evidence of safety has a security + // decision to revisit; silence here is what made the inert key dangerous. + expect(issue!.message).toMatch(/never that it is safe/s); + }); + + it('REJECTS the sibling `vulnerabilities` list, which left with its value type', () => { + const result = PluginSecurityManifestSchema.safeParse({ + ...baseManifest, + vulnerabilities: [{ id: 'V-1', severity: 'critical', title: 't', description: 'd', affectedVersions: [] }], + }); + + expect(result.success).toBe(false); + if (result.success) return; + + const issue = result.error.issues.find((i) => i.path[0] === 'vulnerabilities'); + expect(issue, 'the refusal must name `vulnerabilities`').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.message).toMatch(/`PluginSecurityManifest\.vulnerabilities`.*removed/s); + expect(issue!.message).toMatch(/blocked\s+no install/s); + }); + + it('REJECTS `PluginQualityMetrics.securityScan`, the member that published a VERDICT', () => { + const result = PluginQualityMetricsSchema.safeParse({ + testCoverage: 85, + securityScan: { lastScanDate: '2026-01-15T10:00:00Z', passed: true }, + }); + + expect(result.success).toBe(false); + if (result.success) return; + + const issue = result.error.issues.find((i) => i.path[0] === 'securityScan'); + expect(issue, 'the refusal must name `securityScan`').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(['securityScan']); + expect(issue!.message).toMatch(/`PluginQualityMetrics\.securityScan`.*removed.*ADR-0049/s); + // The prescription must say plainly what the verdict was worth, because a + // consumer that gated on `passed: true` was gating on nothing. + expect(issue!.message).toMatch(/`passed: true` with nothing at all behind it/s); + expect(issue!.message).toMatch(/Delete the key/s); + }); + + it('parses cleanly once the keys are deleted, and grows no such property', () => { + // Absence must stay absence. Neither carrying shape is `.strict()`, so a bare + // deletion would have stripped an authored key in SILENCE (ADR-0104) — this + // pin plus the refusals above are what keep the tombstones honest. + const manifest = PluginSecurityManifestSchema.parse({ ...baseManifest }); + expect(manifest.pluginId).toBe('com.example.plugin'); + expect(manifest).not.toHaveProperty('scanResults'); + expect(manifest).not.toHaveProperty('vulnerabilities'); + + const metrics = PluginQualityMetricsSchema.parse({ testCoverage: 85, codeQuality: 75 }); + expect(metrics.testCoverage).toBe(85); + expect(metrics).not.toHaveProperty('securityScan'); + }); + + it('does not export the retired scan-result defs from ./kernel', async () => { + const kernel = await import('./index'); + for (const name of ['KernelSecurityScanResultSchema', 'KernelSecurityVulnerabilitySchema']) { + expect(kernel, `${name} must not be exported after #15932`).not.toHaveProperty(name); + } + + // Anti-vacuity: this pin means nothing unless the barrel really resolved and + // still exports the neighbours that SURVIVE. Three groups, each load-bearing: + // the manifest's own surviving security vocabulary; the sibling quality + // metrics; and — the scope fence — the SEPARATELY DECLARED, unprefixed pair + // in `plugin-security.zod.ts`, which is a different family with its own + // self-test and is deliberately NOT part of this retirement. + expect(kernel).toHaveProperty('PluginSecurityManifestSchema'); + expect(kernel).toHaveProperty('KernelSecurityPolicySchema'); + expect(kernel).toHaveProperty('SandboxConfigSchema'); + expect(kernel).toHaveProperty('PluginQualityMetricsSchema'); + expect(kernel).toHaveProperty('SecurityScanResultSchema'); + expect(kernel).toHaveProperty('SecurityVulnerabilitySchema'); + }); +}); diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts new file mode 100644 index 00000000000..782c0959b11 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, +// 2026-09-07, maintainer verbatim 「同意」). `KernelSecurityScanResult` declared a +// complete scan report — timestamp, scanner name/version, a passed/failed/warning +// status, vulnerability and code-issue lists, dependency findings, license +// compliance and a six-number summary — and no layer ever emitted, stored, parsed +// or read one. Its only importer of any kind was `PluginSecurityScanner`, a +// type-only import from `packages/core/src/security/security-scanner.ts`, and +// #14919 deleted that file; the census after it landed put every remaining +// reference inside the declaring module itself, against a lit control (five hits +// for `PluginSecurityManifest` in the same file), so the zero is a reading. +// +// Whole-def retirement, not a tombstone: nothing parses this def, so there is no +// author to hand a prescription to and no `${defKey}:${name}` key leaving a live +// shape. `RETIRED_DEFS_BY_MAJOR[18]` plus the semantic entry +// `plugin-security-scan-result-surface-retired` ARE the declaration — the +// #11825 / #8715 shape. The two authorable carriers that pointed here, +// `PluginSecurityManifest.scanResults` and `.vulnerabilities`, are separately +// tombstoned and registered in `RETIRED_KEYS_BY_MAJOR[18]`. +// +// No D2 conversion: a plugin security manifest is a package artifact a publisher +// ships, never a stack collection member and never a stored `sys_metadata` row, +// so the conversion chain has no seam that would see one (the sibling +// `kernel/PluginSecurityManifest:vulnerabilityDisclosure.responseTime` entry +// records the same reasoning for the same schema). +export const entry = 'kernel/KernelSecurityScanResult'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityVulnerability.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityVulnerability.ts new file mode 100644 index 00000000000..142f80b4e63 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityVulnerability.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, +// 2026-09-07, maintainer verbatim 「同意」). The other half of the scan-result +// family: a CVE-shaped vulnerability record (severity, CVSS score, affected and +// fixed versions, exploit/patch availability, remediation, disclosure dates) that +// nothing ever constructed, validated or consulted. It reached this build through +// exactly three referents, all now gone — `KernelSecurityScanResult.vulnerabilities` +// and `.dependencyVulnerabilities[].vulnerability` (that def leaves in the same +// change) and `PluginSecurityManifest.vulnerabilities` (tombstoned). Retired +// together with `KernelSecurityScanResult` because declaring a vulnerability +// vocabulary with no scan to carry it reads as a capability, which is the +// ADR-0049 shape. +// +// Whole-def retirement for the reason its sibling entry records, and the same +// disposition: `RETIRED_DEFS_BY_MAJOR[18]` plus +// `plugin-security-scan-result-surface-retired`, no D2 conversion, no tombstone +// of its own. Its two authorable defaults — +// `KernelSecurityVulnerability:exploitAvailable = false` and +// `:patchAvailable = false` — leave `authorable-defaults/kernel.json` with the +// def; nothing ever parsed this schema, so no released toolchain ever +// materialized either value into an artifact and there is no residue to accept. +// +// ⛔ NOT in scope, and deliberately untouched: the unprefixed +// `SecurityVulnerabilitySchema` / `SecurityScanResultSchema` pair in the sibling +// module `kernel/plugin-security.zod.ts`. Those are separate defs with their own +// self-test and are outside this ruling. +export const entry = 'kernel/KernelSecurityVulnerability'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginQualityMetrics__securityScan.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginQualityMetrics__securityScan.ts new file mode 100644 index 00000000000..f521009ec56 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginQualityMetrics__securityScan.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, +// 2026-09-07, maintainer verbatim 「同意」). `PluginQualityMetrics.securityScan` is +// the scan-result family's sibling on the plugin registry entry, named by the +// ruling alongside it: a last-scan date, per-severity vulnerability counts and a +// `passed` boolean, read by no scanner, registry, installer or UI. The census put +// every reference in `packages/spec/src/kernel/plugin-registry.test.ts` — the +// spec's own self-test and nothing else. +// +// It is the sharper half of the family for an author: `scanResults` published a +// report, but `securityScan.passed` published a VERDICT, so a plugin could ship +// `passed: true` with nothing at all behind it and a consumer reading the +// registry entry had no way to tell that from a real result. +// +// Tombstoned with `retiredKey()`: `PluginQualityMetricsSchema` is a plain +// `z.object`, so a bare deletion would strip an authored block in silence +// (ADR-0104). The key carried NO default of its own — the defaults inside it +// (`critical`/`high`/`medium`/`low = 0`, `passed = false`) fired only for an +// author who wrote the block — so `acceptRetiredDefaultResidue` is not owed: +// there is no value a released toolchain materialized into an artifact whose +// author never typed the key. +// +// No D2 conversion: a plugin registry entry is a published package artifact, not +// a stack collection member or a stored `sys_metadata` row. The ledger channel is +// `plugin-security-scan-result-surface-retired`. +export const entry = 'kernel/PluginQualityMetrics:securityScan'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts new file mode 100644 index 00000000000..05a5568d931 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, +// 2026-09-07, maintainer verbatim 「同意」). `PluginSecurityManifest.scanResults` +// published an array of `KernelSecurityScanResult` on the authorable surface with +// zero authors and zero parsers: no `.parse`/`.safeParse` site existed anywhere +// against the scan-result schemas, so a publisher could declare a clean scan on a +// plugin manifest, be accepted, and get nothing — the declared-not-enforced shape +// Prime Directive #10 names, one layer out from the runtime scanner #14919 +// removed for the same reason. +// +// Tombstoned with `retiredKey()`, not deleted: `PluginSecurityManifestSchema` is a +// plain `z.object`, not `.strict()`, so a bare deletion would strip an authored +// key in silence (ADR-0104) — swapping an inert declaration for an invisible one. +// The value type leaves this build entirely (`RETIRED_DEFS_BY_MAJOR[18]`, +// `kernel/KernelSecurityScanResult`). +// +// No D2 conversion: a security manifest is a package artifact a publisher ships, +// never a stack collection member and never a stored `sys_metadata` row, so the +// chain has no seam that would see one — the disposition the sibling +// `vulnerabilityDisclosure.responseTime` entry on this same schema already +// records. The prescription an author meets is the tombstone itself; the ledger +// channel is `plugin-security-scan-result-surface-retired`. +export const entry = 'kernel/PluginSecurityManifest:scanResults'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__vulnerabilities.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__vulnerabilities.ts new file mode 100644 index 00000000000..e71c5ea25e2 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__vulnerabilities.ts @@ -0,0 +1,22 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #15932 — ADR-0049 enforce-or-remove, decision batch #65. +// `PluginSecurityManifest.vulnerabilities` was an array of +// `KernelSecurityVulnerability` and is this retirement's FORCED CONSEQUENCE +// rather than a name the ruling listed: it was the last authorable referent of a +// def the ruling retires by name, so it cannot survive the def, and keeping the +// def alive only to carry it would be keeping the retired family alive under a +// second name. It is inert on its own terms too — nothing ever wrote the list and +// nothing ever read it, so declaring a known vulnerability against a plugin +// warned nobody and blocked no install. +// +// ⚠️ This is the ONE key outside the four names the #15932 dispatch fenced +// (`KernelSecurityScanResult`, `KernelSecurityVulnerability`, +// `PluginSecurityManifest.scanResults`, `PluginQualityMetrics.securityScan`), and +// it is reported as such on the card and in the landing PR rather than absorbed +// silently. It is not a neighbour retired by proximity — the fence's stated +// concern — it is a referent of a named retiree. +// +// Tombstoned with `retiredKey()` for the reason its `scanResults` sibling records +// (non-strict shape, ADR-0104 silent strip). No D2 conversion, same reasoning. +export const entry = 'kernel/PluginSecurityManifest:vulnerabilities'; diff --git a/packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts b/packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts new file mode 100644 index 00000000000..0b7e626504d --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts @@ -0,0 +1,83 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'plugin-security-scan-result-surface-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a code + // span AND a table cell. + surface: 'the plugin-security scan-result family: the defs ' + + 'KernelSecurityScanResult and KernelSecurityVulnerability ' + + '(kernel/plugin-security-advanced.zod.ts), their two authorable carriers on ' + + 'PluginSecurityManifest — scanResults and vulnerabilities — and the sibling ' + + 'verdict block PluginQualityMetrics.securityScan (kernel/plugin-registry.zod.ts)', + replacement: + 'nothing to re-declare — delete the keys and every import of the two types. Plugin ' + + 'security scanning is not a platform capability and there is no replacement schema. ' + + 'What the platform does still enforce, and what to reach for instead: `permissions` and ' + + '`sandbox` on the same PluginSecurityManifest are unchanged, and artifact provenance is ' + + 'answered by `verifyPluginArtifactIntegrity` and the plugin signature verifier — which ' + + 'tell you an artifact is the one its publisher signed, and never that it is safe. For ' + + 'dependency vulnerabilities use the tools built for it against your own project (npm ' + + 'audit / pnpm audit, Dependabot, the GitHub Advisory Database, OSV) and treat an ' + + 'unaudited third-party plugin as untrusted code. A publisher who used scanResults to ' + + 'advertise diligence keeps the surviving securityContact and vulnerabilityDisclosure ' + + 'blocks, which are contact terms rather than a verdict.', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-07 on #15932 (director seat, decision batch #65, adopted verbatim 「同意」). ' + + 'This is the second half of #14919. That card retired PluginSecurityScanner — a ' + + '@objectstack/core class that shipped as a SECURITY control and could not fail, whose ' + + 'verdict was status "passed" for every plugin it was ever handed. The SCHEMAS the ' + + 'scanner fed survived it, and the scanner had been their only importer of any kind (a ' + + 'type-only import in packages/core/src/security/security-scanner.ts), so the family went ' + + 'from one type-only importer to zero consumers while staying fully published: 27 ' + + 'authorable rows across kernel.json, six api-surface exports, two authorable defaults ' + + 'and two json-schema manifest keys. An author could write any of it, be accepted, and ' + + 'get nothing — declared-not-enforced, Prime Directive #10, one layer out from the class ' + + 'removed for the same reason. The census was taken on origin/main after #14919 landed, ' + + 'with a lit control (five hits for PluginSecurityManifest inside the declaring module) ' + + 'proving the file greppable, and found no .parse or .safeParse site against either ' + + 'schema anywhere in packages/**. securityScan is the sharpest member: scanResults ' + + 'published a report, but securityScan.passed published a VERDICT, so a plugin could ' + + 'declare itself clean with nothing behind it. Route: the two defs leave the build whole ' + + '(RETIRED_DEFS_BY_MAJOR[18]) because nothing parses them and a prescription nobody can ' + + 'receive is not worth its cost; the three authorable keys are retiredKey() tombstones ' + + '(RETIRED_KEYS_BY_MAJOR[18]) because both carrying shapes are non-strict, where a bare ' + + 'deletion is a silent strip (ADR-0104). Why this entry and not a D2 conversion: a plugin ' + + 'security manifest and a plugin registry entry are package artifacts a publisher ships, ' + + 'never stack collection members and never stored sys_metadata rows, so the conversion ' + + 'chain has no seam that would see one — the disposition the sibling ' + + 'kernel-plugin-security-durations-unit-in-key entry already records for this same ' + + 'manifest. No deprecation window (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」). ' + + 'Scope note, recorded rather than acted on: PluginSecurityManifest.vulnerabilities is a ' + + 'forced consequence rather than a name the ruling listed — it was the last authorable ' + + 'referent of KernelSecurityVulnerability and could not outlive the def. Two neighbours ' + + 'the ruling made CONDITIONAL are deliberately untouched here because the repository the ' + + 'condition names, objectstack-ai/cloud, is not reachable from the session that executed ' + + 'this: the marketplace "scanning" status and the incident "malware" type stay exactly as ' + + 'they are, unremoved and not recorded as checked. ' + + '⚠️ The out-of-repo consumer population is NOT MEASURED. @objectstack/spec is published, ' + + 'so this removal is breaking for consumers no download, dependent or source telemetry ' + + 'was consulted for — accepted as an input to the ruling, exactly as #14919 states of its ' + + 'own three exports, and not a reason to soften the removal. #15932, #14919, ADR-0049, ADR-0087.', + acceptanceCriteria: + 'No source imports KernelSecurityScanResult, KernelSecurityVulnerability or either ' + + 'Schema from @objectstack/spec/kernel: both defs are absent from the built kernel ' + + 'barrel and from api-surface/kernel.json, so a TypeScript consumer gets the refusal at ' + + 'compile time at the import site rather than a missing runtime value. Authoring ' + + 'PluginSecurityManifest.scanResults, PluginSecurityManifest.vulnerabilities or ' + + 'PluginQualityMetrics.securityScan fails to compile (input type `never`) and fails to ' + + 'parse with the tombstone prescription naming that key — verified by refusal pins that ' + + 'assert the issue code, the path naming WHICH key was refused, and the prescription ' + + 'text, plus a positive pin that the surrounding manifest still parses and grows no such ' + + 'property. ⚠️ Runtime behaviour is deliberately UNCHANGED and must be verified as such: ' + + 'nothing ever read any of these keys, so deleting one removes no check that was running. ' + + 'A publisher who believed a declared scanResults entry gated anything was never getting ' + + 'that gate; the remediation is to audit with a real tool, not to find a replacement key. ' + + 'The surviving neighbours must still parse and still be exported — permissions, sandbox, ' + + 'policy, codeSigning, certifications, securityContact and vulnerabilityDisclosure on the ' + + 'manifest, testCoverage/documentationScore/codeQuality/conformanceTests on the quality ' + + 'metrics, and the separately-declared SecurityScanResultSchema / ' + + 'SecurityVulnerabilitySchema in kernel/plugin-security.zod.ts, which this change does ' + + 'not touch.', +}; diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index f8ecd4f84a4..97713a745d4 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -10618,6 +10618,85 @@ const step18: MigrationStep = { + 'still carries `globs` keeps serving as stored data; clear it by deleting the key from ' + 'the source manifest and republishing.', }, + { + id: 'plugin-security-scan-result-surface-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a code + // span AND a table cell. + surface: 'the plugin-security scan-result family: the defs ' + + 'KernelSecurityScanResult and KernelSecurityVulnerability ' + + '(kernel/plugin-security-advanced.zod.ts), their two authorable carriers on ' + + 'PluginSecurityManifest — scanResults and vulnerabilities — and the sibling ' + + 'verdict block PluginQualityMetrics.securityScan (kernel/plugin-registry.zod.ts)', + replacement: + 'nothing to re-declare — delete the keys and every import of the two types. Plugin ' + + 'security scanning is not a platform capability and there is no replacement schema. ' + + 'What the platform does still enforce, and what to reach for instead: `permissions` and ' + + '`sandbox` on the same PluginSecurityManifest are unchanged, and artifact provenance is ' + + 'answered by `verifyPluginArtifactIntegrity` and the plugin signature verifier — which ' + + 'tell you an artifact is the one its publisher signed, and never that it is safe. For ' + + 'dependency vulnerabilities use the tools built for it against your own project (npm ' + + 'audit / pnpm audit, Dependabot, the GitHub Advisory Database, OSV) and treat an ' + + 'unaudited third-party plugin as untrusted code. A publisher who used scanResults to ' + + 'advertise diligence keeps the surviving securityContact and vulnerabilityDisclosure ' + + 'blocks, which are contact terms rather than a verdict.', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-07 on #15932 (director seat, decision batch #65, adopted verbatim 「同意」). ' + + 'This is the second half of #14919. That card retired PluginSecurityScanner — a ' + + '@objectstack/core class that shipped as a SECURITY control and could not fail, whose ' + + 'verdict was status "passed" for every plugin it was ever handed. The SCHEMAS the ' + + 'scanner fed survived it, and the scanner had been their only importer of any kind (a ' + + 'type-only import in packages/core/src/security/security-scanner.ts), so the family went ' + + 'from one type-only importer to zero consumers while staying fully published: 27 ' + + 'authorable rows across kernel.json, six api-surface exports, two authorable defaults ' + + 'and two json-schema manifest keys. An author could write any of it, be accepted, and ' + + 'get nothing — declared-not-enforced, Prime Directive #10, one layer out from the class ' + + 'removed for the same reason. The census was taken on origin/main after #14919 landed, ' + + 'with a lit control (five hits for PluginSecurityManifest inside the declaring module) ' + + 'proving the file greppable, and found no .parse or .safeParse site against either ' + + 'schema anywhere in packages/**. securityScan is the sharpest member: scanResults ' + + 'published a report, but securityScan.passed published a VERDICT, so a plugin could ' + + 'declare itself clean with nothing behind it. Route: the two defs leave the build whole ' + + '(RETIRED_DEFS_BY_MAJOR[18]) because nothing parses them and a prescription nobody can ' + + 'receive is not worth its cost; the three authorable keys are retiredKey() tombstones ' + + '(RETIRED_KEYS_BY_MAJOR[18]) because both carrying shapes are non-strict, where a bare ' + + 'deletion is a silent strip (ADR-0104). Why this entry and not a D2 conversion: a plugin ' + + 'security manifest and a plugin registry entry are package artifacts a publisher ships, ' + + 'never stack collection members and never stored sys_metadata rows, so the conversion ' + + 'chain has no seam that would see one — the disposition the sibling ' + + 'kernel-plugin-security-durations-unit-in-key entry already records for this same ' + + 'manifest. No deprecation window (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」). ' + + 'Scope note, recorded rather than acted on: PluginSecurityManifest.vulnerabilities is a ' + + 'forced consequence rather than a name the ruling listed — it was the last authorable ' + + 'referent of KernelSecurityVulnerability and could not outlive the def. Two neighbours ' + + 'the ruling made CONDITIONAL are deliberately untouched here because the repository the ' + + 'condition names, objectstack-ai/cloud, is not reachable from the session that executed ' + + 'this: the marketplace "scanning" status and the incident "malware" type stay exactly as ' + + 'they are, unremoved and not recorded as checked. ' + + '⚠️ The out-of-repo consumer population is NOT MEASURED. @objectstack/spec is published, ' + + 'so this removal is breaking for consumers no download, dependent or source telemetry ' + + 'was consulted for — accepted as an input to the ruling, exactly as #14919 states of its ' + + 'own three exports, and not a reason to soften the removal. #15932, #14919, ADR-0049, ADR-0087.', + acceptanceCriteria: + 'No source imports KernelSecurityScanResult, KernelSecurityVulnerability or either ' + + 'Schema from @objectstack/spec/kernel: both defs are absent from the built kernel ' + + 'barrel and from api-surface/kernel.json, so a TypeScript consumer gets the refusal at ' + + 'compile time at the import site rather than a missing runtime value. Authoring ' + + 'PluginSecurityManifest.scanResults, PluginSecurityManifest.vulnerabilities or ' + + 'PluginQualityMetrics.securityScan fails to compile (input type `never`) and fails to ' + + 'parse with the tombstone prescription naming that key — verified by refusal pins that ' + + 'assert the issue code, the path naming WHICH key was refused, and the prescription ' + + 'text, plus a positive pin that the surrounding manifest still parses and grows no such ' + + 'property. ⚠️ Runtime behaviour is deliberately UNCHANGED and must be verified as such: ' + + 'nothing ever read any of these keys, so deleting one removes no check that was running. ' + + 'A publisher who believed a declared scanResults entry gated anything was never getting ' + + 'that gate; the remediation is to audit with a real tool, not to find a replacement key. ' + + 'The surviving neighbours must still parse and still be exported — permissions, sandbox, ' + + 'policy, codeSigning, certifications, securityContact and vulnerabilityDisclosure on the ' + + 'manifest, testCoverage/documentationScore/codeQuality/conformanceTests on the quality ' + + 'metrics, and the separately-declared SecurityScanResultSchema / ' + + 'SecurityVulnerabilitySchema in kernel/plugin-security.zod.ts, which this change does ' + + 'not touch.', + }, { id: 'plugin-security-scanner-retired', surface: @@ -14734,6 +14813,73 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly> // (`packages/core/src/health-monitor.ts`), never authored. See // `kernel-plugin-health-report-durations-unit-in-key`. 'kernel/PluginHealthReport:metrics.uptime', + // #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, + // 2026-09-07, maintainer verbatim 「同意」). `PluginQualityMetrics.securityScan` is + // the scan-result family's sibling on the plugin registry entry, named by the + // ruling alongside it: a last-scan date, per-severity vulnerability counts and a + // `passed` boolean, read by no scanner, registry, installer or UI. The census put + // every reference in `packages/spec/src/kernel/plugin-registry.test.ts` — the + // spec's own self-test and nothing else. + // + // It is the sharper half of the family for an author: `scanResults` published a + // report, but `securityScan.passed` published a VERDICT, so a plugin could ship + // `passed: true` with nothing at all behind it and a consumer reading the + // registry entry had no way to tell that from a real result. + // + // Tombstoned with `retiredKey()`: `PluginQualityMetricsSchema` is a plain + // `z.object`, so a bare deletion would strip an authored block in silence + // (ADR-0104). The key carried NO default of its own — the defaults inside it + // (`critical`/`high`/`medium`/`low = 0`, `passed = false`) fired only for an + // author who wrote the block — so `acceptRetiredDefaultResidue` is not owed: + // there is no value a released toolchain materialized into an artifact whose + // author never typed the key. + // + // No D2 conversion: a plugin registry entry is a published package artifact, not + // a stack collection member or a stored `sys_metadata` row. The ledger channel is + // `plugin-security-scan-result-surface-retired`. + 'kernel/PluginQualityMetrics:securityScan', + // #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, + // 2026-09-07, maintainer verbatim 「同意」). `PluginSecurityManifest.scanResults` + // published an array of `KernelSecurityScanResult` on the authorable surface with + // zero authors and zero parsers: no `.parse`/`.safeParse` site existed anywhere + // against the scan-result schemas, so a publisher could declare a clean scan on a + // plugin manifest, be accepted, and get nothing — the declared-not-enforced shape + // Prime Directive #10 names, one layer out from the runtime scanner #14919 + // removed for the same reason. + // + // Tombstoned with `retiredKey()`, not deleted: `PluginSecurityManifestSchema` is a + // plain `z.object`, not `.strict()`, so a bare deletion would strip an authored + // key in silence (ADR-0104) — swapping an inert declaration for an invisible one. + // The value type leaves this build entirely (`RETIRED_DEFS_BY_MAJOR[18]`, + // `kernel/KernelSecurityScanResult`). + // + // No D2 conversion: a security manifest is a package artifact a publisher ships, + // never a stack collection member and never a stored `sys_metadata` row, so the + // chain has no seam that would see one — the disposition the sibling + // `vulnerabilityDisclosure.responseTime` entry on this same schema already + // records. The prescription an author meets is the tombstone itself; the ledger + // channel is `plugin-security-scan-result-surface-retired`. + 'kernel/PluginSecurityManifest:scanResults', + // #15932 — ADR-0049 enforce-or-remove, decision batch #65. + // `PluginSecurityManifest.vulnerabilities` was an array of + // `KernelSecurityVulnerability` and is this retirement's FORCED CONSEQUENCE + // rather than a name the ruling listed: it was the last authorable referent of a + // def the ruling retires by name, so it cannot survive the def, and keeping the + // def alive only to carry it would be keeping the retired family alive under a + // second name. It is inert on its own terms too — nothing ever wrote the list and + // nothing ever read it, so declaring a known vulnerability against a plugin + // warned nobody and blocked no install. + // + // ⚠️ This is the ONE key outside the four names the #15932 dispatch fenced + // (`KernelSecurityScanResult`, `KernelSecurityVulnerability`, + // `PluginSecurityManifest.scanResults`, `PluginQualityMetrics.securityScan`), and + // it is reported as such on the card and in the landing PR rather than absorbed + // silently. It is not a neighbour retired by proximity — the fence's stated + // concern — it is a referent of a named retiree. + // + // Tombstoned with `retiredKey()` for the reason its `scanResults` sibling records + // (non-strict shape, ADR-0104 silent strip). No D2 conversion, same reasoning. + 'kernel/PluginSecurityManifest:vulnerabilities', // #15678 (stack card 3/6 of #14478) — ruling B. // `PluginSecurityManifest.vulnerabilityDisclosure.responseTime` said "Expected // response time in hours" in prose and nothing else. Renamed to @@ -17213,6 +17359,57 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // keeps emitting — see `18.kernel__PluginStartupResult__health.ts`. Route 3; // the D3 semantic entry `startup-orchestrator-retired` carries the record. 'kernel/HealthStatus', + // #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, + // 2026-09-07, maintainer verbatim 「同意」). `KernelSecurityScanResult` declared a + // complete scan report — timestamp, scanner name/version, a passed/failed/warning + // status, vulnerability and code-issue lists, dependency findings, license + // compliance and a six-number summary — and no layer ever emitted, stored, parsed + // or read one. Its only importer of any kind was `PluginSecurityScanner`, a + // type-only import from `packages/core/src/security/security-scanner.ts`, and + // #14919 deleted that file; the census after it landed put every remaining + // reference inside the declaring module itself, against a lit control (five hits + // for `PluginSecurityManifest` in the same file), so the zero is a reading. + // + // Whole-def retirement, not a tombstone: nothing parses this def, so there is no + // author to hand a prescription to and no `${defKey}:${name}` key leaving a live + // shape. `RETIRED_DEFS_BY_MAJOR[18]` plus the semantic entry + // `plugin-security-scan-result-surface-retired` ARE the declaration — the + // #11825 / #8715 shape. The two authorable carriers that pointed here, + // `PluginSecurityManifest.scanResults` and `.vulnerabilities`, are separately + // tombstoned and registered in `RETIRED_KEYS_BY_MAJOR[18]`. + // + // No D2 conversion: a plugin security manifest is a package artifact a publisher + // ships, never a stack collection member and never a stored `sys_metadata` row, + // so the conversion chain has no seam that would see one (the sibling + // `kernel/PluginSecurityManifest:vulnerabilityDisclosure.responseTime` entry + // records the same reasoning for the same schema). + 'kernel/KernelSecurityScanResult', + // #15932 — ADR-0049 enforce-or-remove (director seat, decision batch #65, + // 2026-09-07, maintainer verbatim 「同意」). The other half of the scan-result + // family: a CVE-shaped vulnerability record (severity, CVSS score, affected and + // fixed versions, exploit/patch availability, remediation, disclosure dates) that + // nothing ever constructed, validated or consulted. It reached this build through + // exactly three referents, all now gone — `KernelSecurityScanResult.vulnerabilities` + // and `.dependencyVulnerabilities[].vulnerability` (that def leaves in the same + // change) and `PluginSecurityManifest.vulnerabilities` (tombstoned). Retired + // together with `KernelSecurityScanResult` because declaring a vulnerability + // vocabulary with no scan to carry it reads as a capability, which is the + // ADR-0049 shape. + // + // Whole-def retirement for the reason its sibling entry records, and the same + // disposition: `RETIRED_DEFS_BY_MAJOR[18]` plus + // `plugin-security-scan-result-surface-retired`, no D2 conversion, no tombstone + // of its own. Its two authorable defaults — + // `KernelSecurityVulnerability:exploitAvailable = false` and + // `:patchAvailable = false` — leave `authorable-defaults/kernel.json` with the + // def; nothing ever parsed this schema, so no released toolchain ever + // materialized either value into an artifact and there is no residue to accept. + // + // ⛔ NOT in scope, and deliberately untouched: the unprefixed + // `SecurityVulnerabilitySchema` / `SecurityScanResultSchema` pair in the sibling + // module `kernel/plugin-security.zod.ts`. Those are separate defs with their own + // self-test and are outside this ruling. + 'kernel/KernelSecurityVulnerability', // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes // the widened surface). Part of the whole-module removal of From c4ad0c2a6a9804549a8c141ca7a90ff4e592812d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 17:42:57 +0000 Subject: [PATCH 2/3] fix(spec): name a resolving record for two citations the board no longer serves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:issue-citations` went red at 0a1bac8977 with eight dangling sites across five files: issue 14919 at six of them and issue 8715 at two. Measured cause, from the gate's own `--probe-cause`: deleted, all eight — not transferred, not mistyped. Probed with a lit control beside each, since both dead numbers sit next to live ones: 8714 404 / 8715 404 / 8716 200, and 14918 404 / 14919 404 / 14920 200. Scattered pairs, not a contiguous band, which is what deletion-by-author looks like. Both were live references when the prose was written. ⛔ No number is guessed and none is swapped for a plausible neighbour. Each site keeps its number in prose and now says it no longer resolves, then names a record that DOES — verified by probe, not inferred: - issue 14919 -> PR #15930, `feat(core)!: retire PluginSecurityScanner`, merged 2026-09-05, whose body opens with a closing line naming that very issue number. Probe: 200. - issue 8715 -> #11825, the half of the pair this tree cites together that still resolves, and the same whole-def disposition shape. Probe: 200. The `#` sigil is what the gate judges (`CITATION_RE`); a bare number in prose is not a citation, so the number survives verbatim and the reference stops dangling. `migrations/registry.ts` is GENERATED and was NOT hand-edited: its three copies come from the two entry files, re-emitted by `gen:migration-registry`. The module docblock feeds a reference page, so `check:generated --fix` regenerated `content/docs/references/kernel/plugin-security-advanced.mdx` through `gen:docs`. ⛔ Nothing else moves: no schema, no key, no registry entry, no changeset level, no authorable row. Comments and the prose they generate, only. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx --- .../references/kernel/plugin-security-advanced.mdx | 4 +++- .../spec/src/kernel/plugin-security-advanced.zod.ts | 7 +++++-- .../18.kernel__KernelSecurityScanResult.ts | 7 +++++-- ...18.kernel__PluginSecurityManifest__scanResults.ts | 5 +++-- packages/spec/src/migrations/registry.ts | 12 ++++++++---- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/content/docs/references/kernel/plugin-security-advanced.mdx b/content/docs/references/kernel/plugin-security-advanced.mdx index a492afe4dc4..353fabcd3f9 100644 --- a/content/docs/references/kernel/plugin-security-advanced.mdx +++ b/content/docs/references/kernel/plugin-security-advanced.mdx @@ -20,7 +20,9 @@ Features: `KernelSecurityVulnerability`, `PluginSecurityManifest.scanResults` and its sibling list `.vulnerabilities` — was retired under ADR-0049 enforce-or-remove (#15932), after the runtime scanner that was its last type-only importer went -the same way (#14919). Nothing on this platform scans a plugin. +the same way. That scanner retirement was issue 14919 — a number DELETED from +the board and no longer resolving; its live record is PR #15930, which landed +the removal. Nothing on this platform scans a plugin. **Source:** `packages/spec/src/kernel/plugin-security-advanced.zod.ts` diff --git a/packages/spec/src/kernel/plugin-security-advanced.zod.ts b/packages/spec/src/kernel/plugin-security-advanced.zod.ts index 0f3af37f506..bf87e3c32fd 100644 --- a/packages/spec/src/kernel/plugin-security-advanced.zod.ts +++ b/packages/spec/src/kernel/plugin-security-advanced.zod.ts @@ -19,7 +19,9 @@ import { EvaluatedExpressionInputSchema } from '../shared/expression.zod'; * `KernelSecurityVulnerability`, `PluginSecurityManifest.scanResults` and its * sibling list `.vulnerabilities` — was retired under ADR-0049 enforce-or-remove * (#15932), after the runtime scanner that was its last type-only importer went - * the same way (#14919). Nothing on this platform scans a plugin. + * the same way. That scanner retirement was issue 14919 — a number DELETED from + * the board and no longer resolving; its live record is PR #15930, which landed + * the removal. Nothing on this platform scans a plugin. */ /** @@ -588,7 +590,8 @@ export const PluginSecurityManifestSchema = lazySchema(() => z.object({ * Tombstone: the scan-result surface is RETIRED (#15932, ADR-0049 * enforce-or-remove). `KernelSecurityScanResult` and * `KernelSecurityVulnerability` left this build with it — the last importer - * of either type went with `PluginSecurityScanner` (#14919). Not a bare + * of either type went with `PluginSecurityScanner` (issue 14919, a number + * deleted from the board; live record: PR #15930). Not a bare * deletion: this shape is not `.strict()`, so zod would strip an authored * key in silence (ADR-0104). */ diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts index 782c0959b11..0c5f1261f89 100644 --- a/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__KernelSecurityScanResult.ts @@ -7,7 +7,8 @@ // compliance and a six-number summary — and no layer ever emitted, stored, parsed // or read one. Its only importer of any kind was `PluginSecurityScanner`, a // type-only import from `packages/core/src/security/security-scanner.ts`, and -// #14919 deleted that file; the census after it landed put every remaining +// PR #15930 deleted that file (it closed issue 14919, a number since deleted +// from the board and no longer resolving); the census after it landed put every remaining // reference inside the declaring module itself, against a lit control (five hits // for `PluginSecurityManifest` in the same file), so the zero is a reading. // @@ -15,7 +16,9 @@ // author to hand a prescription to and no `${defKey}:${name}` key leaving a live // shape. `RETIRED_DEFS_BY_MAJOR[18]` plus the semantic entry // `plugin-security-scan-result-surface-retired` ARE the declaration — the -// #11825 / #8715 shape. The two authorable carriers that pointed here, +// #11825 shape (its sibling in the pair this tree usually cites, issue 8715, is +// a number deleted from the board; 11825 is the half that still resolves). +// The two authorable carriers that pointed here, // `PluginSecurityManifest.scanResults` and `.vulnerabilities`, are separately // tombstoned and registered in `RETIRED_KEYS_BY_MAJOR[18]`. // diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts index 05a5568d931..fee45f4b620 100644 --- a/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginSecurityManifest__scanResults.ts @@ -6,8 +6,9 @@ // zero authors and zero parsers: no `.parse`/`.safeParse` site existed anywhere // against the scan-result schemas, so a publisher could declare a clean scan on a // plugin manifest, be accepted, and get nothing — the declared-not-enforced shape -// Prime Directive #10 names, one layer out from the runtime scanner #14919 -// removed for the same reason. +// Prime Directive #10 names, one layer out from the runtime scanner that PR +// #15930 removed for the same reason (that PR closed issue 14919, a number since +// deleted from the board and no longer resolving). // // Tombstoned with `retiredKey()`, not deleted: `PluginSecurityManifestSchema` is a // plain `z.object`, not `.strict()`, so a bare deletion would strip an authored diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 97713a745d4..f41bdb8c164 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -14844,8 +14844,9 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly> // zero authors and zero parsers: no `.parse`/`.safeParse` site existed anywhere // against the scan-result schemas, so a publisher could declare a clean scan on a // plugin manifest, be accepted, and get nothing — the declared-not-enforced shape - // Prime Directive #10 names, one layer out from the runtime scanner #14919 - // removed for the same reason. + // Prime Directive #10 names, one layer out from the runtime scanner that PR + // #15930 removed for the same reason (that PR closed issue 14919, a number since + // deleted from the board and no longer resolving). // // Tombstoned with `retiredKey()`, not deleted: `PluginSecurityManifestSchema` is a // plain `z.object`, not `.strict()`, so a bare deletion would strip an authored @@ -17366,7 +17367,8 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // compliance and a six-number summary — and no layer ever emitted, stored, parsed // or read one. Its only importer of any kind was `PluginSecurityScanner`, a // type-only import from `packages/core/src/security/security-scanner.ts`, and - // #14919 deleted that file; the census after it landed put every remaining + // PR #15930 deleted that file (it closed issue 14919, a number since deleted + // from the board and no longer resolving); the census after it landed put every remaining // reference inside the declaring module itself, against a lit control (five hits // for `PluginSecurityManifest` in the same file), so the zero is a reading. // @@ -17374,7 +17376,9 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // author to hand a prescription to and no `${defKey}:${name}` key leaving a live // shape. `RETIRED_DEFS_BY_MAJOR[18]` plus the semantic entry // `plugin-security-scan-result-surface-retired` ARE the declaration — the - // #11825 / #8715 shape. The two authorable carriers that pointed here, + // #11825 shape (its sibling in the pair this tree usually cites, issue 8715, is + // a number deleted from the board; 11825 is the half that still resolves). + // The two authorable carriers that pointed here, // `PluginSecurityManifest.scanResults` and `.vulnerabilities`, are separately // tombstoned and registered in `RETIRED_KEYS_BY_MAJOR[18]`. // From 3e0a06d0b521e0df9d54dd2356b860f84d9a45ab Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 17:58:42 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs(spec):=20correct=20the=20carve-out=20r?= =?UTF-8?q?ecords=20=E2=80=94=20two=20of=20the=20three=20coordinates=20wer?= =?UTF-8?q?e=20stale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the at-tier review, item ③.3. The POSTURE on the carve-out was right and is kept verbatim: the marketplace 'scanning' status stays untouched and ⛔ NOT recorded as checked. What was wrong is a COORDINATE — two of the three files those records name no longer exist, so the present-tense clause "stay exactly as they are, unremoved" was an assertion nobody had measured, and false for one half. Verified here by shape rather than taken on report, since stale coordinates are the defect being repaired. Tree entries on this branch AND on origin/main: marketplace.zod.ts 1 ('scanning' live) marketplace-admin.zod.ts 0 system/incident-response.zod.ts 0 'malware' in any *.zod.ts 0 (lit control: 'scanning' returns a live declaration, so the zero is a reading) The incident 'malware' type was a member of system/IncidentCategory, and the whole incident-response family was retired by #15513 — maintainer ruling 2026-09-05, two days BEFORE the 2026-09-07 ruling that made it conditional. marketplace-admin.zod was deleted outright with the cloud subpath (#16526). ⇒ The conditional question is ONE enum member wide, not three, and the records now say so — so whoever finally runs the objectstack-ai/cloud producer grep knows its real size. ⛔ The 'scanning' half stays genuinely unmeasured; it does not become "checked and clean". Also repaired in the same prose, same defect class as the previous commit: ten citations of the deleted issue 14919 that this card's own records were handing on. None was visible to check:issue-citations — FOLLOW-UPS.md and .changeset are not judged surfaces, and the semantic entry's are inside string literals, which the comment-prose projection blanks. The changeset is what an upgrading agent greps after a tombstone error, so a dead number there is the defect the gate exists to prevent, one surface out of its reach. Same repair form: the number is kept in prose, said to no longer resolve, and the live record named. ⛔ Nothing about the retirement changes: no schema, key, def, registry entry, changeset level, authorable row or generated artefact. check:generated reports all 15 up to date. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx --- ...in-security-scan-result-surface-retired.md | 25 ++++++++++---- docs/qa/platform-checklist/FOLLOW-UPS.md | 34 +++++++++++++------ ...in-security-scan-result-retirement.test.ts | 3 +- ...in-security-scan-result-surface-retired.ts | 24 +++++++++---- packages/spec/src/migrations/registry.ts | 24 +++++++++---- 5 files changed, 79 insertions(+), 31 deletions(-) diff --git a/.changeset/15932-plugin-security-scan-result-surface-retired.md b/.changeset/15932-plugin-security-scan-result-surface-retired.md index fc3533a7abe..f3e068d3567 100644 --- a/.changeset/15932-plugin-security-scan-result-surface-retired.md +++ b/.changeset/15932-plugin-security-scan-result-surface-retired.md @@ -2,13 +2,14 @@ '@objectstack/spec': minor --- -feat(spec)!: retire the plugin-security scan-result surface — zero consumers after #14919 (#15932) +feat(spec)!: retire the plugin-security scan-result surface — zero consumers after the scanner retirement (#15932) **BREAKING** — the plugin-security scan-result family is removed. ADR-0049 enforce-or-remove; maintainer ruling 2026-09-07 (director seat, decision batch #65), adopted verbatim 「同意」. -This is the second half of #14919. That change retired `PluginSecurityScanner`, +This is the second half of the scanner retirement — issue 14919, a number since +deleted from the board, landed as PR #15930. That change retired `PluginSecurityScanner`, the `@objectstack/core` class that shipped as a security control and returned `status: "passed"` for every plugin it was ever handed. The **schemas** it fed survived it — and that scanner's type-only import was their only importer of any @@ -71,13 +72,23 @@ is to audit with a real tool, not to find a replacement key. ⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no download, dependent or source -telemetry was consulted for — exactly as #14919's changeset says of its own three -exports. That was an input to the ruling, not a reason to soften the removal. +telemetry was consulted for — exactly as that retirement's own changeset says of its +three exports. That was an input to the ruling, not a reason to soften the removal. -⛔ **Untouched, and not checked:** the marketplace `'scanning'` status and the -incident `'malware'` type. The ruling made them conditional on a producer grep of +⛔ **Untouched, and not checked:** the marketplace `'scanning'` status +(`marketplace.zod.ts`). The ruling made it conditional on a producer grep of `objectstack-ai/cloud`, and that repository was not reachable from the session -that executed this card. +that executed this card, so it stays exactly as it is and its absence from this +diff is not evidence about it. + +⚠️ **The two members the ruling paired with it were ALREADY GONE** — measured on +this tree, not assumed. The incident `'malware'` type was a member of +`system/IncidentCategory`, and the whole incident-response family was retired by +#15513 (maintainer ruling 2026-09-05 — two days *before* the 2026-09-07 ruling +that made it conditional). `marketplace-admin.zod.ts` was deleted outright with +the cloud subpath (#16526). Both files return zero tree entries here, against a +lit control where `'scanning'` still returns a live declaration. So the +conditional question is **one** enum member wide, not three. `Clause-②: yes (narrowing)` — a published surface is removed: six exports leave the built `.d.ts` and three authorable keys stop being writable, so the accept diff --git a/docs/qa/platform-checklist/FOLLOW-UPS.md b/docs/qa/platform-checklist/FOLLOW-UPS.md index 10652b66738..27246027d7c 100644 --- a/docs/qa/platform-checklist/FOLLOW-UPS.md +++ b/docs/qa/platform-checklist/FOLLOW-UPS.md @@ -183,9 +183,9 @@ governance hole. | surface | evidence | the deadness, precisely | |---|---|---| -| ~~`KernelSecurityScanResult` / `KernelSecurityVulnerability` / `PluginSecurityManifest.scanResults`~~ (`packages/spec/src/kernel/plugin-security-advanced.zod.ts`) | no `.parse`/`.safeParse` site anywhere; **zero** consumers of any kind since #14919 retired the dead scanner that was the last type-only importer | **CLOSED by removal, #15932** (ruling below). The count this row carried was **22**; the retirement measured **27** authorable rows for the named surface — 8 + 17 for the two defs, plus one each for `PluginSecurityManifest.scanResults` and `PluginQualityMetrics.securityScan`. The 22 is not reconciled, only superseded. | +| ~~`KernelSecurityScanResult` / `KernelSecurityVulnerability` / `PluginSecurityManifest.scanResults`~~ (`packages/spec/src/kernel/plugin-security-advanced.zod.ts`) | no `.parse`/`.safeParse` site anywhere; **zero** consumers of any kind since the scanner retirement (issue 14919, a number since deleted from the board; live record PR #15930) removed the dead scanner that was the last type-only importer | **CLOSED by removal, #15932** (ruling below). The count this row carried was **22**; the retirement measured **27** authorable rows for the named surface — 8 + 17 for the two defs, plus one each for `PluginSecurityManifest.scanResults` and `PluginQualityMetrics.securityScan`. The 22 is not reconciled, only superseded. | | ~~`PluginQualityMetrics.securityScan`~~ (`packages/spec/src/kernel/plugin-registry.zod.ts`) | spec self-test only | **CLOSED by removal, #15932** — tombstoned with the family it mirrored. | -| Marketplace/incident scan vocab (`marketplace.zod.ts` 'scanning' status, `marketplace-admin.zod.ts,193`, `incident-response.zod.ts` 'malware') | declared-only enum members, no producer in this repo | Cloud/EE surface. Same shape as the `'failed'`/`'expired'` upload statuses #7667 had to close: declared, published, no writer. | +| Marketplace scan vocab — `marketplace.zod.ts` `'scanning'` status. ⛔ Still live, still unchecked. ⚠️ The two siblings this row used to name are GONE and the coordinates were stale: `marketplace-admin.zod.ts` left with the cloud subpath (#16526), and `incident-response.zod.ts` — which carried `'malware'` on `system/IncidentCategory` — was retired whole by #15513. Both verified as zero tree entries. | declared-only enum member, no producer in this repo | Cloud/EE surface. Same shape as the `'failed'`/`'expired'` upload statuses #7667 had to close: declared, published, no writer. | | MetadataPlugin FS scan + `metadata-fs` boot scan (`packages/metadata/src/plugin.ts,270` — `watch ?? false`; `packages/runtime/src/standalone-stack.ts` hard-off; `metadata-fs` unwired from any `os dev`/`os serve` lane) | unit-pinned in-package only | No reachable fixture from any shipped boot; if a future lane wires `metadata-fs`, the boot-scan/watcher dot-entry divergence is the risk to test first. | **The first two rows above were CLOSED by removal in #15932** (maintainer ruling, @@ -194,15 +194,27 @@ left the build whole (`RETIRED_DEFS_BY_MAJOR[18]`), and the authorable carriers `PluginSecurityManifest.scanResults`, its sibling list `.vulnerabilities` and `PluginQualityMetrics.securityScan` — are `retiredKey()` tombstones registered in `RETIRED_KEYS_BY_MAJOR[18]`. "Declare an owner to enforce" was refused by name: it would -rebuild the scanner #14919 had just retired for the same reason. ⛔ Do not re-derive it. - -⚠️ **The third row is NOT closed and was NOT checked.** The ruling made the marketplace -`'scanning'` status and the incident `'malware'` type conditional on a producer grep of -`objectstack-ai/cloud`, and that repository was not reachable from the session that -executed #15932 — so those members are untouched, and their absence from a diff is not -evidence about them. The ready-to-run command is on #15932. - -The scanner row further down was **CLOSED by removal** in #14919 (maintainer ruling, +rebuild the scanner issue 14919 had just retired for the same reason. ⛔ Do not re-derive it. + +⚠️ **The third row is NOT closed and was NOT checked — and it is now ONE member, not +three.** The ruling made the marketplace `'scanning'` status and the incident +`'malware'` type conditional on a producer grep of `objectstack-ai/cloud`, and that +repository was not reachable from the session that executed #15932 — so `'scanning'` is +untouched, and its absence from a diff is not evidence about it. The ready-to-run command +is on #15932. + +⚠️ **But `'malware'` was already gone when that ruling was written**, which the ruling +could not have known. It was a member of `system/IncidentCategory`, and the whole +incident-response family was retired by #15513 — maintainer ruling 2026-09-05, **two days +before** the 2026-09-07 ruling that made it conditional (see +`incident-response-family-retired`). `marketplace-admin.zod.ts` left with the cloud +subpath (#16526). Verified on this tree by shape, not taken on report: both files return +zero tree entries on this branch and on `origin/main`, and no `*.zod.ts` names +`malware` at all — against a lit control where `'scanning'` returns a live declaration +in `marketplace.zod.ts`. ⇒ The cloud grep this row still owes is about **one** enum +member, and ⛔ that one is still genuinely unmeasured. + +The scanner row further down was **CLOSED by removal** in PR #15930, for issue 14919 (maintainer ruling, director summon #14, decision batch #42): the class, its barrel export, its `packages/core/examples/` demonstration and the `PHASE2_IMPLEMENTATION.md` section that advertised it are gone, and that section now states plainly that plugin security scanning diff --git a/packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts b/packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts index d1f29315de3..33f307e149e 100644 --- a/packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts +++ b/packages/spec/src/kernel/plugin-security-scan-result-retirement.test.ts @@ -18,7 +18,8 @@ import { PluginQualityMetricsSchema } from './plugin-registry.zod'; // plus `PluginSecurityManifest.vulnerabilities`, the last authorable referent of // the second def and therefore a forced consequence of retiring it. // -// This is the second half of #14919, which retired `PluginSecurityScanner` — the +// This is the second half of the scanner retirement (issue 14919, a number since +// deleted from the board; live record PR #15930), which retired `PluginSecurityScanner` — the // `@objectstack/core` class that shipped as a security control and returned // `status: "passed"` for every plugin it was ever handed. That scanner was the // family's ONLY importer of any kind (a type-only import), so its deletion moved diff --git a/packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts b/packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts index 0b7e626504d..c531c10f9f8 100644 --- a/packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts +++ b/packages/spec/src/migrations/entries/semantic/18.plugin-security-scan-result-surface-retired.ts @@ -25,7 +25,8 @@ export const entry: SemanticMigration = { + 'blocks, which are contact terms rather than a verdict.', reason: 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-07 on #15932 (director seat, decision batch #65, adopted verbatim 「同意」). ' - + 'This is the second half of #14919. That card retired PluginSecurityScanner — a ' + + 'This is the second half of the scanner retirement — issue 14919, a number since ' + + 'DELETED from the board, landed as PR #15930. That card retired PluginSecurityScanner — a ' + '@objectstack/core class that shipped as a SECURITY control and could not fail, whose ' + 'verdict was status "passed" for every plugin it was ever handed. The SCHEMAS the ' + 'scanner fed survived it, and the scanner had been their only importer of any kind (a ' @@ -34,7 +35,7 @@ export const entry: SemanticMigration = { + 'authorable rows across kernel.json, six api-surface exports, two authorable defaults ' + 'and two json-schema manifest keys. An author could write any of it, be accepted, and ' + 'get nothing — declared-not-enforced, Prime Directive #10, one layer out from the class ' - + 'removed for the same reason. The census was taken on origin/main after #14919 landed, ' + + 'removed for the same reason. The census was taken on origin/main after that removal landed, ' + 'with a lit control (five hits for PluginSecurityManifest inside the declaring module) ' + 'proving the file greppable, and found no .parse or .safeParse site against either ' + 'schema anywhere in packages/**. securityScan is the sharpest member: scanResults ' @@ -54,12 +55,23 @@ export const entry: SemanticMigration = { + 'referent of KernelSecurityVulnerability and could not outlive the def. Two neighbours ' + 'the ruling made CONDITIONAL are deliberately untouched here because the repository the ' + 'condition names, objectstack-ai/cloud, is not reachable from the session that executed ' - + 'this: the marketplace "scanning" status and the incident "malware" type stay exactly as ' - + 'they are, unremoved and not recorded as checked. ' + + 'this: the marketplace "scanning" status stays exactly as it is — unremoved, and NOT ' + + 'recorded as checked. Its two siblings were MEASURED rather than assumed, and the ' + + 'record is corrected here: both were ALREADY GONE when that ruling was written. The ' + + 'incident "malware" type was a member of system/IncidentCategory, and the whole ' + + 'incident-response family was retired by #15513 (maintainer ruling 2026-09-05 — two ' + + 'days BEFORE the 2026-09-07 ruling that made it conditional); see ' + + 'incident-response-family-retired. And marketplace-admin.zod.ts was deleted outright ' + + 'with the cloud subpath (#16526); see cloud-subpath-retired. Verified on this tree by ' + + 'shape: both files return zero tree entries and no *.zod.ts names malware at all, ' + + 'against a lit control where "scanning" still returns a live declaration in ' + + 'marketplace.zod.ts. So the conditional question is ONE enum member wide, not three, ' + + 'and the objectstack-ai/cloud producer grep it still owes is that much smaller. ' + '⚠️ The out-of-repo consumer population is NOT MEASURED. @objectstack/spec is published, ' + 'so this removal is breaking for consumers no download, dependent or source telemetry ' - + 'was consulted for — accepted as an input to the ruling, exactly as #14919 states of its ' - + 'own three exports, and not a reason to soften the removal. #15932, #14919, ADR-0049, ADR-0087.', + + 'was consulted for — accepted as an input to the ruling, exactly as that retirement states ' + + 'of its own three exports, and not a reason to soften the removal. #15932, PR #15930 ' + + '(for the deleted issue 14919), ADR-0049, ADR-0087.', acceptanceCriteria: 'No source imports KernelSecurityScanResult, KernelSecurityVulnerability or either ' + 'Schema from @objectstack/spec/kernel: both defs are absent from the built kernel ' diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index f41bdb8c164..fa8f1e18e9d 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -10641,7 +10641,8 @@ const step18: MigrationStep = { + 'blocks, which are contact terms rather than a verdict.', reason: 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-07 on #15932 (director seat, decision batch #65, adopted verbatim 「同意」). ' - + 'This is the second half of #14919. That card retired PluginSecurityScanner — a ' + + 'This is the second half of the scanner retirement — issue 14919, a number since ' + + 'DELETED from the board, landed as PR #15930. That card retired PluginSecurityScanner — a ' + '@objectstack/core class that shipped as a SECURITY control and could not fail, whose ' + 'verdict was status "passed" for every plugin it was ever handed. The SCHEMAS the ' + 'scanner fed survived it, and the scanner had been their only importer of any kind (a ' @@ -10650,7 +10651,7 @@ const step18: MigrationStep = { + 'authorable rows across kernel.json, six api-surface exports, two authorable defaults ' + 'and two json-schema manifest keys. An author could write any of it, be accepted, and ' + 'get nothing — declared-not-enforced, Prime Directive #10, one layer out from the class ' - + 'removed for the same reason. The census was taken on origin/main after #14919 landed, ' + + 'removed for the same reason. The census was taken on origin/main after that removal landed, ' + 'with a lit control (five hits for PluginSecurityManifest inside the declaring module) ' + 'proving the file greppable, and found no .parse or .safeParse site against either ' + 'schema anywhere in packages/**. securityScan is the sharpest member: scanResults ' @@ -10670,12 +10671,23 @@ const step18: MigrationStep = { + 'referent of KernelSecurityVulnerability and could not outlive the def. Two neighbours ' + 'the ruling made CONDITIONAL are deliberately untouched here because the repository the ' + 'condition names, objectstack-ai/cloud, is not reachable from the session that executed ' - + 'this: the marketplace "scanning" status and the incident "malware" type stay exactly as ' - + 'they are, unremoved and not recorded as checked. ' + + 'this: the marketplace "scanning" status stays exactly as it is — unremoved, and NOT ' + + 'recorded as checked. Its two siblings were MEASURED rather than assumed, and the ' + + 'record is corrected here: both were ALREADY GONE when that ruling was written. The ' + + 'incident "malware" type was a member of system/IncidentCategory, and the whole ' + + 'incident-response family was retired by #15513 (maintainer ruling 2026-09-05 — two ' + + 'days BEFORE the 2026-09-07 ruling that made it conditional); see ' + + 'incident-response-family-retired. And marketplace-admin.zod.ts was deleted outright ' + + 'with the cloud subpath (#16526); see cloud-subpath-retired. Verified on this tree by ' + + 'shape: both files return zero tree entries and no *.zod.ts names malware at all, ' + + 'against a lit control where "scanning" still returns a live declaration in ' + + 'marketplace.zod.ts. So the conditional question is ONE enum member wide, not three, ' + + 'and the objectstack-ai/cloud producer grep it still owes is that much smaller. ' + '⚠️ The out-of-repo consumer population is NOT MEASURED. @objectstack/spec is published, ' + 'so this removal is breaking for consumers no download, dependent or source telemetry ' - + 'was consulted for — accepted as an input to the ruling, exactly as #14919 states of its ' - + 'own three exports, and not a reason to soften the removal. #15932, #14919, ADR-0049, ADR-0087.', + + 'was consulted for — accepted as an input to the ruling, exactly as that retirement states ' + + 'of its own three exports, and not a reason to soften the removal. #15932, PR #15930 ' + + '(for the deleted issue 14919), ADR-0049, ADR-0087.', acceptanceCriteria: 'No source imports KernelSecurityScanResult, KernelSecurityVulnerability or either ' + 'Schema from @objectstack/spec/kernel: both defs are absent from the built kernel '