Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2f560a5
feat(lab): add CL-10 public evidence projector
Wibias Aug 12, 2026
60db580
ci: add temporary CL-10 focused validation
Wibias Aug 12, 2026
0e15a46
fix(lab): validate registry source commit correctly
Wibias Aug 12, 2026
ef128e8
test(lab): extend CL-10 public projection contract
Wibias Aug 12, 2026
7a9b1c5
feat(lab): complete CL-10 public projection boundary
Wibias Aug 12, 2026
4c455d8
test(lab): define CL-10 signing and export storage
Wibias Aug 12, 2026
5e1830e
feat(lab): add CL-10 signing and local export storage
Wibias Aug 12, 2026
7ea7b43
fix(lab): derive public key through typed PEM input
Wibias Aug 12, 2026
bf88f7d
test(lab): define CL-10 community revocation boundary
Wibias Aug 12, 2026
6088e41
feat(lab): quarantine CL-10 community evidence
Wibias Aug 12, 2026
812a3a8
test(lab): use canonical public scenario versions
Wibias Aug 12, 2026
15351fa
test(lab): define CL-10 purge export interaction
Wibias Aug 12, 2026
84e2d5a
test(lab): isolate CL-10 purge provenance case
Wibias Aug 12, 2026
74d79b5
feat(lab): purge locally-originated public evidence copies
Wibias Aug 12, 2026
f11d600
feat(lab): include CL-10 copies in sensitive export purge
Wibias Aug 12, 2026
f86b89c
test(lab): define cross-publisher community continuity
Wibias Aug 12, 2026
a4cb639
ci: cover CL-10 publisher continuity
Wibias Aug 12, 2026
489e4da
fix(lab): scope community bundles by publisher continuity
Wibias Aug 12, 2026
8fb10e3
fix(lab): preserve third-party publisher continuity during purge
Wibias Aug 12, 2026
345877c
test(lab): define CL-10 local operator surfaces
Wibias Aug 12, 2026
7dce9c7
ci: cover CL-10 local operator surfaces
Wibias Aug 12, 2026
0215df7
feat(lab): add CL-10 local public evidence operator
Wibias Aug 12, 2026
aee7fd7
feat(lab): export CL-10 local operator API
Wibias Aug 12, 2026
4ffd552
chore: apply CL-10 Task 6
Wibias Aug 12, 2026
63b724f
chore: retry CL-10 Task 6 patch
Wibias Aug 12, 2026
fd7f804
feat(lab): add CL-10 local public evidence surfaces
Wibias Aug 12, 2026
90ad980
test(gui): define CL-10 community evidence context
Wibias Aug 12, 2026
edf2d83
ci: cover CL-10 Matrix community context
Wibias Aug 12, 2026
3c4eae2
chore: apply CL-10 Task 7
Wibias Aug 12, 2026
0b563d6
chore: retry CL-10 Task 7
Wibias Aug 12, 2026
47b11fd
chore: finalize CL-10 Task 7 patch
Wibias Aug 12, 2026
4d4ee47
feat(gui): show quarantined CL-10 community evidence
Wibias Aug 12, 2026
e8080b4
docs(lab): record CL-10 implementation validation state
Wibias Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/cl10-focus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: CL-10 focused validation

on:
push:
branches:
- feat/cl-10-public-evidence-runtime

permissions:
contents: read

jobs:
focused:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.3.14
- run: bun install --frozen-lockfile
- run: bun test tests/lab-public-evidence.test.ts tests/lab-public-evidence-signature.test.ts tests/lab-community-evidence.test.ts tests/lab-community-publisher-continuity.test.ts tests/lab-public-surfaces.test.ts
- run: bun x tsc --noEmit
- run: cd gui && bun install --frozen-lockfile
- run: cd gui && bun test tests/compatibility-community-evidence.test.ts tests/compatibility-lab-i18n.test.ts
- run: cd gui && bun x tsc -b --pretty false
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# CL-10 Public Evidence Validation State

Validated implementation scope: CL-10.1 through CL-10.4 only. CL-10.5 remote publishing remains blocked by contract.

## Implemented

- Closed, independently versioned public evidence DTOs and runtime validators.
- Domain-separated public subject, record, bundle, artifact, publisher, and revocation identities.
- Repo-owned, versioned, content-addressed public route registry authority.
- Fail-closed protocol/route/task privacy projection. Private route dimensions are never dropped to broaden a public claim.
- Ed25519 publisher continuity with an installation-local restricted private key and public-only bundle identity.
- Deterministic signed local bundles and content-addressed local export storage.
- Same-publisher revocation with bounded targets and idempotent identical replay.
- Quarantined `community_untrusted_v1` import/cache with cryptographic validation followed by repository authority validation.
- Publisher-scoped community identity so identical content signed by different publishers can coexist and revoke independently.
- Sensitive `export` purge integration for generated exports and provably locally-originated community copies while preserving third-party evidence.
- Explicit local CLI/API preview, export, verify, import, and community-list surfaces.
- Compatibility Matrix read-only community context, labelled non-authoritative and kept separate from the canonical local verdict.

## Hard stops preserved

- No remote publish command or management endpoint.
- No arbitrary upload URL or remote transport implementation.
- No automatic telemetry or background public-evidence upload.
- No imported community write to `compatibility.jsonl` or `compatibility.sqlite`.
- No community evidence input to canonical local verdicts, routing, Router Intelligence, or CL-08.
- A valid signature yields `cryptographically_valid`, never `locally_verified`.

## Focused TDD evidence before closure run

- Public projection/registry/privacy tests: GREEN.
- Signing/local export tests: GREEN.
- Community/revocation/publisher-continuity tests: GREEN.
- Purge interaction tests: GREEN.
- Local CLI/API surface tests with network canaries: GREEN.
- Compatibility Matrix community parser/render/i18n tests: GREEN.
- Root TypeScript: GREEN on the implemented core/operator slices.
- GUI TypeScript and GUI lint: GREEN on the Matrix slice.

## Closure still required on exact final head

- Focused CL-10 tests including existing Lab purge regressions.
- Root TypeScript and privacy scan.
- Relevant Lab query/ledger/CLI/management tests.
- GUI targeted tests, lint, build, and React Doctor.
- Full Cross-platform CI.
- Final changed-file/static audit confirming the CL-10.5 remote-publish hard stop and zero feedback into routing/local verdict/CL-08.
47 changes: 46 additions & 1 deletion gui/src/i18n/lab-translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ export type LabSupplementKey =
| "artifact.present"
| "artifact.corrupt"
| "artifact.purged_unavailable"
| "selectVerdict";
| "selectVerdict"
| "community.title"
| "community.notLocalVerdict"
| "community.bundles"
| "community.activeRecords"
| "community.revokedRecords";

const en: Record<LabCatalogKey, string> = {
"lab.title": "Compatibility Lab",
Expand Down Expand Up @@ -427,55 +432,95 @@ const supplements: Record<LabLocale, Record<LabSupplementKey, string>> = {
"artifact.corrupt": "Corrupt",
"artifact.purged_unavailable": "Purged / unavailable",
selectVerdict: "View verdict for {subject}",
"community.title": "Community evidence",
"community.notLocalVerdict": "Untrusted read-only context. Not included in this local verdict.",
"community.bundles": "Bundles",
"community.activeRecords": "Active records",
"community.revokedRecords": "Revoked records",
},
de: {
subjectKindUnknown: "Unbekannt",
"artifact.present": "Vorhanden",
"artifact.corrupt": "Beschädigt",
"artifact.purged_unavailable": "Gelöscht / nicht verfügbar",
selectVerdict: "Urteil für {subject} anzeigen",
"community.title": "Community-Evidenz",
"community.notLocalVerdict": "Nicht vertrauenswürdiger Nur-Lese-Kontext. Nicht Teil dieses lokalen Urteils.",
"community.bundles": "Pakete",
"community.activeRecords": "Aktive Einträge",
"community.revokedRecords": "Widerrufene Einträge",
},
ko: {
subjectKindUnknown: "알 수 없음",
"artifact.present": "있음",
"artifact.corrupt": "손상됨",
"artifact.purged_unavailable": "삭제됨 / 사용할 수 없음",
selectVerdict: "{subject}의 판정 보기",
"community.title": "커뮤니티 증거",
"community.notLocalVerdict": "신뢰되지 않는 읽기 전용 컨텍스트입니다. 이 로컬 판정에는 포함되지 않습니다.",
"community.bundles": "번들",
"community.activeRecords": "활성 레코드",
"community.revokedRecords": "폐기된 레코드",
},
zh: {
subjectKindUnknown: "未知",
"artifact.present": "存在",
"artifact.corrupt": "已损坏",
"artifact.purged_unavailable": "已清除 / 不可用",
selectVerdict: "查看 {subject} 的判定",
"community.title": "社区证据",
"community.notLocalVerdict": "不受信任的只读上下文。不计入此本地判定。",
"community.bundles": "证据包",
"community.activeRecords": "有效记录",
"community.revokedRecords": "已撤销记录",
},
"zh-TW": {
subjectKindUnknown: "未知",
"artifact.present": "存在",
"artifact.corrupt": "已損壞",
"artifact.purged_unavailable": "已清除 / 不可用",
selectVerdict: "查看 {subject} 的判定",
"community.title": "社群證據",
"community.notLocalVerdict": "不受信任的唯讀脈絡。不計入此本地判定。",
"community.bundles": "證據包",
"community.activeRecords": "有效記錄",
"community.revokedRecords": "已撤銷記錄",
},
ru: {
subjectKindUnknown: "Неизвестно",
"artifact.present": "Доступен",
"artifact.corrupt": "Повреждён",
"artifact.purged_unavailable": "Удалён / недоступен",
selectVerdict: "Открыть вердикт для {subject}",
"community.title": "Данные сообщества",
"community.notLocalVerdict": "Недоверенный контекст только для чтения. Не входит в этот локальный вердикт.",
"community.bundles": "Пакеты",
"community.activeRecords": "Активные записи",
"community.revokedRecords": "Отозванные записи",
},
ja: {
subjectKindUnknown: "不明",
"artifact.present": "存在",
"artifact.corrupt": "破損",
"artifact.purged_unavailable": "削除済み / 利用不可",
selectVerdict: "{subject} の判定を表示",
"community.title": "コミュニティ証拠",
"community.notLocalVerdict": "信頼されていない読み取り専用コンテキストです。このローカル判定には含まれません。",
"community.bundles": "バンドル",
"community.activeRecords": "有効なレコード",
"community.revokedRecords": "取り消されたレコード",
},
tr: {
subjectKindUnknown: "Bilinmiyor",
"artifact.present": "Mevcut",
"artifact.corrupt": "Bozuk",
"artifact.purged_unavailable": "Temizlenmiş / kullanılamıyor",
selectVerdict: "{subject} için kararı görüntüle",
"community.title": "Topluluk kanıtı",
"community.notLocalVerdict": "Güvenilmeyen salt okunur bağlam. Bu yerel karara dahil değildir.",
"community.bundles": "Paketler",
"community.activeRecords": "Etkin kayıtlar",
"community.revokedRecords": "Geri çekilen kayıtlar",
},
};

Expand Down
11 changes: 11 additions & 0 deletions gui/src/pages/CompatibilityMatrix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,17 @@ function DetailPane({
</dl>
</section>
)}
{detail.community && detail.community.evidence.length > 0 && (
<section className="lab-detail-section" data-testid="lab-community-evidence">
<h4>{labSupplement(locale, "community.title")}</h4>
<p className="muted">{labSupplement(locale, "community.notLocalVerdict")}</p>
<dl className="lab-detail-meta">
<div><dt>{labSupplement(locale, "community.bundles")}</dt><dd>{detail.community.evidence.length}</dd></div>
<div><dt>{labSupplement(locale, "community.activeRecords")}</dt><dd>{detail.community.evidence.reduce((total, row) => total + row.activeRecordCount, 0)}</dd></div>
<div><dt>{labSupplement(locale, "community.revokedRecords")}</dt><dd>{detail.community.evidence.reduce((total, row) => total + row.revokedRecordCount, 0)}</dd></div>
</dl>
</section>
)}
{detail.observations.length > 0 && (
<section className="lab-detail-section">
<h4>{t("lab.detailObservations")}</h4>
Expand Down
82 changes: 81 additions & 1 deletion gui/src/pages/compatibility-matrix-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,80 @@ export async function fetchPassiveProductionSummary(
return parsePassiveProductionSummary(raw);
}

export type CommunityEvidenceSummaryRowDto = {
trustClass: "community_untrusted_v1";
status: "cryptographically_valid";
bundleId: string;
publisherKeyId: string;
activeRecordCount: number;
revokedRecordCount: number;
};

export type CommunityEvidenceContextDto = {
evidence: CommunityEvidenceSummaryRowDto[];
trustClass: "community_untrusted_v1";
locallyVerified: false;
};

function hasOnlyKeys(raw: Record<string, unknown>, allowed: readonly string[]): boolean {
const allowedSet = new Set(allowed);
return Object.keys(raw).every(key => allowedSet.has(key));
}

function isSha256Hex(value: unknown): value is string {
return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
}

function isNonNegativeInteger(value: unknown): value is number {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
}

export function parseCommunityEvidenceContext(raw: unknown): CommunityEvidenceContextDto | null {
if (!isPlainObject(raw)
|| !hasOnlyKeys(raw, ["evidence", "trustClass", "locallyVerified"])
|| raw.trustClass !== "community_untrusted_v1"
|| raw.locallyVerified !== false
|| !Array.isArray(raw.evidence)
|| raw.evidence.length > 4096) {
return null;
}
const evidence: CommunityEvidenceSummaryRowDto[] = [];
for (const value of raw.evidence) {
if (!isPlainObject(value)
|| !hasOnlyKeys(value, [
"trustClass", "status", "bundleId", "publisherKeyId",
"activeRecordCount", "revokedRecordCount",
])
|| value.trustClass !== "community_untrusted_v1"
|| value.status !== "cryptographically_valid"
|| !isSha256Hex(value.bundleId)
|| !isSha256Hex(value.publisherKeyId)
|| !isNonNegativeInteger(value.activeRecordCount)
|| !isNonNegativeInteger(value.revokedRecordCount)) {
return null;
}
evidence.push({
trustClass: "community_untrusted_v1",
status: "cryptographically_valid",
bundleId: value.bundleId,
publisherKeyId: value.publisherKeyId,
activeRecordCount: value.activeRecordCount,
revokedRecordCount: value.revokedRecordCount,
});
}
return { evidence, trustClass: "community_untrusted_v1", locallyVerified: false };
}

export async function fetchCommunityEvidenceContext(
apiBase: string,
signal: AbortSignal,
): Promise<CommunityEvidenceContextDto> {
const raw = await fetchLabJson<unknown>(apiBase, "/api/lab/public/community", signal);
const context = parseCommunityEvidenceContext(raw);
if (!context) throw invalidResponse();
return context;
}

export type LabPageData = {
status: LabStatusDto;
verdicts: VerdictDto[];
Expand Down Expand Up @@ -296,6 +370,7 @@ export type VerdictDetailData = {
events: LabEventDto[];
artifacts: ArtifactMetadataDto[];
production: PassiveProductionSummaryDto | null;
community: CommunityEvidenceContextDto | null;
};

async function mapSettledBounded<TItem, TResult>(
Expand Down Expand Up @@ -337,7 +412,7 @@ export async function fetchVerdictDetail(
layer: verdict.evidenceLayer,
suiteId: verdict.suiteId,
};
const [subject, observations, events, artifacts, production] = await Promise.all([
const [subject, observations, events, artifacts, production, community] = await Promise.all([
fetchSubjectDetail(apiBase, verdict.subjectId, signal),
fetchAllObservations(apiBase, observationFilters, signal),
mapSettledBounded(eventIds, DETAIL_CONCURRENCY, signal, id => fetchEventById(apiBase, id, signal)),
Expand All @@ -346,6 +421,10 @@ export async function fetchVerdictDetail(
if (signal.aborted) throw error;
return null;
}),
fetchCommunityEvidenceContext(apiBase, signal).catch(error => {
if (signal.aborted) throw error;
return null;
}),
]);
return {
subject,
Expand All @@ -354,5 +433,6 @@ export async function fetchVerdictDetail(
events,
artifacts,
production,
community,
};
}
58 changes: 58 additions & 0 deletions gui/tests/compatibility-community-evidence.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect, test } from "bun:test";
import {
parseCommunityEvidenceContext,
type CommunityEvidenceContextDto,
} from "../src/pages/compatibility-matrix-api";
import { labSupplement, type LabLocale } from "../src/i18n/lab-translations";

const LOCALES: LabLocale[] = ["en", "de", "ja", "ko", "ru", "tr", "zh", "zh-TW"];

function validContext(): CommunityEvidenceContextDto {
return {
trustClass: "community_untrusted_v1",
locallyVerified: false,
evidence: [
{
trustClass: "community_untrusted_v1",
status: "cryptographically_valid",
bundleId: "a".repeat(64),
publisherKeyId: "b".repeat(64),
activeRecordCount: 3,
revokedRecordCount: 1,
},
],
};
}

test("Compatibility Matrix parses only quarantined community evidence context", () => {
expect(parseCommunityEvidenceContext(validContext())).toEqual(validContext());
expect(parseCommunityEvidenceContext({ ...validContext(), locallyVerified: true })).toBeNull();
expect(parseCommunityEvidenceContext({ ...validContext(), trustClass: "local" })).toBeNull();
expect(parseCommunityEvidenceContext({
...validContext(),
evidence: [{ ...validContext().evidence[0]!, activeRecordCount: -1 }],
})).toBeNull();
expect(parseCommunityEvidenceContext({
...validContext(),
evidence: [{ ...validContext().evidence[0]!, status: "locally_verified" }],
})).toBeNull();
});

test("Compatibility Matrix community copy is localized and explicitly non-authoritative", () => {
for (const locale of LOCALES) {
expect(labSupplement(locale, "community.title")).toBeTruthy();
expect(labSupplement(locale, "community.notLocalVerdict")).toBeTruthy();
expect(labSupplement(locale, "community.bundles")).toBeTruthy();
expect(labSupplement(locale, "community.activeRecords")).toBeTruthy();
expect(labSupplement(locale, "community.revokedRecords")).toBeTruthy();
}
expect(labSupplement("en", "community.notLocalVerdict")).toMatch(/untrusted|not included|local verdict/i);
});

test("Compatibility Matrix renders community evidence as separate context, never a combined score", async () => {
const source = await Bun.file(new URL("../src/pages/CompatibilityMatrix.tsx", import.meta.url)).text();
expect(source).toContain('data-testid="lab-community-evidence"');
expect(source).toContain('labSupplement(locale, "community.notLocalVerdict")');
expect(source).not.toMatch(/combined.?score/i);
expect(source).not.toMatch(/community.*verdict\s*=|verdict\s*=.*community/i);
});
Loading
Loading