|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec)!: retire the plugin-security scan-result surface — zero consumers after the scanner retirement (#15932) |
| 6 | + |
| 7 | +**BREAKING** — the plugin-security scan-result family is removed. ADR-0049 |
| 8 | +enforce-or-remove; maintainer ruling 2026-09-07 (director seat, decision batch |
| 9 | +#65), adopted verbatim 「同意」. |
| 10 | + |
| 11 | +This is the second half of the scanner retirement — issue 14919, a number since |
| 12 | +deleted from the board, landed as PR #15930. That change retired `PluginSecurityScanner`, |
| 13 | +the `@objectstack/core` class that shipped as a security control and returned |
| 14 | +`status: "passed"` for every plugin it was ever handed. The **schemas** it fed |
| 15 | +survived it — and that scanner's type-only import was their only importer of any |
| 16 | +kind, so the family went from one type-only importer to **zero consumers** while |
| 17 | +staying fully published: 27 authorable rows on `authorable-surface/kernel.json`, |
| 18 | +six `api-surface` exports, two authorable defaults and two json-schema manifest |
| 19 | +keys, with no `.parse` or `.safeParse` site against either schema anywhere. An |
| 20 | +author could write any of it, be accepted, and get nothing. That is the |
| 21 | +declared-not-enforced shape, one layer out from the class removed for the same |
| 22 | +reason. "Declare an owner to enforce" was refused by name: it would rebuild the |
| 23 | +scanner just retired. |
| 24 | + |
| 25 | +### FROM → TO |
| 26 | + |
| 27 | +| removed | what to write instead | |
| 28 | +| --- | --- | |
| 29 | +| `KernelSecurityScanResult`, `KernelSecurityScanResultParsed`, `KernelSecurityScanResultSchema` (exports) | nothing — delete the import. No replacement type exists. | |
| 30 | +| `KernelSecurityVulnerability`, `KernelSecurityVulnerabilityParsed`, `KernelSecurityVulnerabilitySchema` (exports) | nothing — delete the import. No replacement type exists. | |
| 31 | +| `PluginSecurityManifest.scanResults` | delete the key | |
| 32 | +| `PluginSecurityManifest.vulnerabilities` | delete the key | |
| 33 | +| `PluginQualityMetrics.securityScan` | delete the key | |
| 34 | + |
| 35 | +**The one-line fix: delete the keys and every import of the two types.** Plugin |
| 36 | +security scanning is not a platform capability and there is no replacement |
| 37 | +schema. What the platform does still enforce is unchanged: `permissions` and |
| 38 | +`sandbox` on the same `PluginSecurityManifest`, and artifact provenance through |
| 39 | +`verifyPluginArtifactIntegrity` and the plugin signature verifier — which tell |
| 40 | +you an artifact is the one its publisher signed, and never that it is safe. For |
| 41 | +dependency vulnerabilities use the tools built for it against your own project |
| 42 | +(`npm audit` / `pnpm audit`, Dependabot, the GitHub Advisory Database, OSV), and |
| 43 | +treat an unaudited third-party plugin as untrusted code. A publisher who used |
| 44 | +`scanResults` to advertise diligence keeps the surviving `securityContact` and |
| 45 | +`vulnerabilityDisclosure` blocks, which are contact terms rather than a verdict. |
| 46 | + |
| 47 | +⚠️ Runtime behaviour is deliberately **unchanged**. Nothing ever read any of |
| 48 | +these keys, so deleting one removes no check that was running. A consumer that |
| 49 | +gated on `securityScan.passed === true` was gating on nothing — the remediation |
| 50 | +is to audit with a real tool, not to find a replacement key. |
| 51 | + |
| 52 | +### The retirement kit |
| 53 | + |
| 54 | +- The two **defs** leave the build whole — `RETIRED_DEFS_BY_MAJOR[18]` |
| 55 | + (`kernel/KernelSecurityScanResult`, `kernel/KernelSecurityVulnerability`) — |
| 56 | + because nothing parses them, so there is no author a tombstone could reach. |
| 57 | +- The three **authorable keys** are `retiredKey()` tombstones registered in |
| 58 | + `RETIRED_KEYS_BY_MAJOR[18]`. Neither carrying shape is `.strict()`, so a bare |
| 59 | + deletion would strip an authored key in silence (ADR-0104): the tombstone is |
| 60 | + audible in both channels — `tsc` (input type `never`) and the parse, which |
| 61 | + raises the prescription itself. |
| 62 | +- **No D2 conversion.** A plugin security manifest and a plugin registry entry |
| 63 | + are package artifacts a publisher ships, never stack collection members and |
| 64 | + never stored `sys_metadata` rows, so the chain has no seam that would see one |
| 65 | + — the disposition the sibling `kernel-plugin-security-durations-unit-in-key` |
| 66 | + entry already records for this same manifest. The D3 semantic entry |
| 67 | + `plugin-security-scan-result-surface-retired` carries the judgement. |
| 68 | +- `PluginSecurityManifest.vulnerabilities` is a **forced consequence**, not one |
| 69 | + of the four names the ruling listed: it was the last authorable referent of |
| 70 | + `KernelSecurityVulnerability` and could not outlive the def. |
| 71 | +- **No deprecation window** (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」). |
| 72 | + |
| 73 | +⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` |
| 74 | +is published, so this is breaking for consumers no download, dependent or source |
| 75 | +telemetry was consulted for — exactly as that retirement's own changeset says of its |
| 76 | +three exports. That was an input to the ruling, not a reason to soften the removal. |
| 77 | + |
| 78 | +⛔ **Untouched, and not checked:** the marketplace `'scanning'` status |
| 79 | +(`marketplace.zod.ts`). The ruling made it conditional on a producer grep of |
| 80 | +`objectstack-ai/cloud`, and that repository was not reachable from the session |
| 81 | +that executed this card, so it stays exactly as it is and its absence from this |
| 82 | +diff is not evidence about it. |
| 83 | + |
| 84 | +⚠️ **The two members the ruling paired with it were ALREADY GONE** — measured on |
| 85 | +this tree, not assumed. The incident `'malware'` type was a member of |
| 86 | +`system/IncidentCategory`, and the whole incident-response family was retired by |
| 87 | +#15513 (maintainer ruling 2026-09-05 — two days *before* the 2026-09-07 ruling |
| 88 | +that made it conditional). `marketplace-admin.zod.ts` was deleted outright with |
| 89 | +the cloud subpath (#16526). Both files return zero tree entries here, against a |
| 90 | +lit control where `'scanning'` still returns a live declaration. So the |
| 91 | +conditional question is **one** enum member wide, not three. |
| 92 | + |
| 93 | +`Clause-②: yes (narrowing)` — a published surface is removed: six exports leave |
| 94 | +the built `.d.ts` and three authorable keys stop being writable, so the accept |
| 95 | +set a consumer writes against narrows. Nothing is widened and nothing is |
| 96 | +renamed. Contract-review tier. |
| 97 | + |
| 98 | +<!-- adr-0087: registered plugin-security-scan-result-surface-retired --> |
0 commit comments