Area
Security / trust
Context
Scanner reports are declared as individually named optional fields — skillSpectorAnalysis, aigAnalysis, llmAnalysis — repeated across four artifact tables in convex/schema.ts, then read back by name in convex/lib/scannerReports.ts and rolled up in convex/lib/securityScanPolicy.ts.
Adding a fourth source therefore touches four table declarations, the reader, the dispatcher, the rollup and the audit UI. The cost is not in the analysis; it is in the shape of the storage. That cost falls on every future source equally, which is the part worth fixing once.
Two things already work and this RFC changes neither. Verdicts are keyed by artifact sha256, so a report is bound to the exact bytes that were published rather than to a name or a version string. That binding is the reason external evidence can be trusted at all here.
The authority model in docs/security-audits.md should stay unchanged:
Skill audits include findings from Tencent Zhuque Lab's A.I.G scanner. A.I.G checks agent instructions for vulnerability patterns and supplies supporting evidence to ClawScan's artifact-wide review. It does not issue ClawHub's final verdict or independently block installation.
Evidence into the rollup, never the blocking verdict. This proposal keeps that rule and asks for no exception to it.
Goals
- Let a new verdict source register without a schema migration across four tables.
- Keep the existing per-scanner report shapes, so no stored report needs rewriting.
- Keep
sha256 binding and the evidence-not-verdict authority model exactly as they are.
Non-goals
- No change to who issues the final verdict. External sources supply evidence.
- No thresholds, evasion indicators or vendor-private signals are discussed here.
- No change to the existing three sources' report shapes or their keys.
Proposal
Replace the named fields with one map, keeping the existing per-scanner validator shapes as the value type:
scanners: v.optional(v.record(v.string(), scannerReportValidator))
scannerReportValidator is the shape aigAnalysisValidator already defines — status, issueCount, findings[], scannerVersion?, summary?, error?, checkedAt — so no existing report needs rewriting. A migration writes the three current fields into scanners under their present keys and leaves the old fields readable for one release.
A source then registers a key, a display name and a trust tier. The rollup reads the map instead of three names.
Examples
Before, per table:
skillSpectorAnalysis: v.optional(skillSpectorAnalysisValidator),
aigAnalysis: v.optional(aigAnalysisValidator),
llmAnalysis: v.optional(llmAnalysisValidator),
After:
scanners: v.optional(v.record(v.string(), scannerReportValidator)),
// { skillspector: {...}, aig: {...}, llm: {...} }
Edge case — a source returns nothing: the key is absent, which is already how an absent named field reads today.
Edge case — two sources disagree: the map holds both; the rollup decides. Today this cannot arise across an arbitrary number of sources because the set is fixed at three.
User impact
Authors and users see no change; the audit page renders the same verdicts from a different shape. API consumers reading the named fields keep working for one release, then read scanners. Maintainers gain a registration path that does not require touching the schema. External contributors proposing a source have a smaller, reviewable diff.
Disclosure
We are a self-interested party. MolTrust runs a skill auditor that would register as one such source. It differs from a linter in one respect relevant here: it binds a verdict to a signed, on-chain-anchored W3C Verifiable Credential over a canonical skill hash, so a third party can recompute the claim without asking us. A track record only its issuer can compute is a reputation service; one any party can recompute is evidence.
We are proposing the slot, not asking to be privileged in it. If the maintainers conclude the map is not worth the migration, that answer is usable and we will implement against the named-field pattern instead.
Open questions
- Is the migration worth doing now, or should a fourth source follow the existing named-field pattern and the map wait for a fifth?
- Should a registering source declare a trust tier, or is a flat "evidence, never verdict" rule for all non-first-party sources simpler and safer?
- What should the UI show when two sources disagree? The A.I.G precedent covers agreement; disagreement between an external attestation and a static scan is arguably itself the useful signal.
- Is there an appetite for requiring external sources to publish their check list and version, so a verdict can be reproduced later? We publish ours and would be glad to see it required of everyone.
Feedback deadline
2026-10-02
Area
Security / trust
Context
Scanner reports are declared as individually named optional fields —
skillSpectorAnalysis,aigAnalysis,llmAnalysis— repeated across four artifact tables inconvex/schema.ts, then read back by name inconvex/lib/scannerReports.tsand rolled up inconvex/lib/securityScanPolicy.ts.Adding a fourth source therefore touches four table declarations, the reader, the dispatcher, the rollup and the audit UI. The cost is not in the analysis; it is in the shape of the storage. That cost falls on every future source equally, which is the part worth fixing once.
Two things already work and this RFC changes neither. Verdicts are keyed by artifact
sha256, so a report is bound to the exact bytes that were published rather than to a name or a version string. That binding is the reason external evidence can be trusted at all here.The authority model in
docs/security-audits.mdshould stay unchanged:Evidence into the rollup, never the blocking verdict. This proposal keeps that rule and asks for no exception to it.
Goals
sha256binding and the evidence-not-verdict authority model exactly as they are.Non-goals
Proposal
Replace the named fields with one map, keeping the existing per-scanner validator shapes as the value type:
scannerReportValidatoris the shapeaigAnalysisValidatoralready defines —status,issueCount,findings[],scannerVersion?,summary?,error?,checkedAt— so no existing report needs rewriting. A migration writes the three current fields intoscannersunder their present keys and leaves the old fields readable for one release.A source then registers a key, a display name and a trust tier. The rollup reads the map instead of three names.
Examples
Before, per table:
After:
Edge case — a source returns nothing: the key is absent, which is already how an absent named field reads today.
Edge case — two sources disagree: the map holds both; the rollup decides. Today this cannot arise across an arbitrary number of sources because the set is fixed at three.
User impact
Authors and users see no change; the audit page renders the same verdicts from a different shape. API consumers reading the named fields keep working for one release, then read
scanners. Maintainers gain a registration path that does not require touching the schema. External contributors proposing a source have a smaller, reviewable diff.Disclosure
We are a self-interested party. MolTrust runs a skill auditor that would register as one such source. It differs from a linter in one respect relevant here: it binds a verdict to a signed, on-chain-anchored W3C Verifiable Credential over a canonical skill hash, so a third party can recompute the claim without asking us. A track record only its issuer can compute is a reputation service; one any party can recompute is evidence.
We are proposing the slot, not asking to be privileged in it. If the maintainers conclude the map is not worth the migration, that answer is usable and we will implement against the named-field pattern instead.
Open questions
Feedback deadline
2026-10-02