Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
247d2d3
feat(lab): CL-02 immutable evidence ledger and SQLite projection
Wibias Aug 9, 2026
cb94cf0
docs(devlog): record CL-02 draft PR #1333 and implementation tip
Wibias Aug 9, 2026
07bc292
docs(devlog): point CL-02 stack tip at current branch HEAD
Wibias Aug 9, 2026
96ecd65
fix(lab): harden CL-02 evidence ledger per review findings
Wibias Aug 9, 2026
cf626d1
test(lab): add CL-02 review regression coverage and update status
Wibias Aug 9, 2026
1251d31
fix(lab): close CL-02 phase-2 independent-review blockers
Wibias Aug 9, 2026
00785fd
fix(lab): reject symlink targets before artifact create
Wibias Aug 9, 2026
afc6697
fix(lab): lstat symlink squatters before artifact create
Wibias Aug 9, 2026
40725d6
fix(lab): centralize evidence producer version
Wibias Aug 9, 2026
1eb173d
fix(lab): fail closed when sanitizing evidence
Wibias Aug 9, 2026
f7c0a8c
fix(lab): enforce restricted state directories
Wibias Aug 9, 2026
0afa852
fix(lab): validate suite manifest authority
Wibias Aug 9, 2026
527d97f
fix(lab): bound invalidation target lookup
Wibias Aug 9, 2026
4f6f9ab
fix(lab): make purge artifact retention fail closed
Wibias Aug 9, 2026
21906ac
fix(lab): constrain projection enum columns
Wibias Aug 9, 2026
2af46f3
fix(lab): make sensitive purge progress explicit
Wibias Aug 9, 2026
a12a555
fix(lab): complete ledger appends across short writes
Wibias Aug 9, 2026
3dddcda
fix(lab): reject POSIX paths before ledger admission
Wibias Aug 9, 2026
f3d878a
fix(lab): verify artifact digests by class
Wibias Aug 9, 2026
441b782
fix(lab): persist real conformance evidence metadata
Wibias Aug 9, 2026
dca7624
fix(lab): enforce verification contracts and freshness
Wibias Aug 9, 2026
6ed1838
fix(lab): make verdict projection contract-safe
Wibias Aug 9, 2026
0644000
fix(lab): rebuild projection atomically
Wibias Aug 9, 2026
9ba59cd
fix(lab): accept measured runner timestamps
Wibias Aug 9, 2026
1ac3d32
test(lab): cover phase-2 evidence fixes
Wibias Aug 9, 2026
daab377
fix(lab): classify artifact filesystem failures
Wibias Aug 9, 2026
e5af56a
fix(lab): remove raw ledger production reader
Wibias Aug 9, 2026
02ca5b5
docs(lab): document sensitive purge exception
Wibias Aug 9, 2026
eecec3a
refactor(lab): isolate validation error type
Wibias Aug 9, 2026
0c9dc4d
refactor(lab): break validation import cycle
Wibias Aug 9, 2026
1eed4ff
fix(lab): validate claim source event ids
Wibias Aug 9, 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
57 changes: 54 additions & 3 deletions devlog/_plan/260807_compatibility_lab/001_pr_stack_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ independent review, blockers, and whether a later phase is authorized.
| Phase | Branch | Starting/base SHA | Accepted head | PR | State |
|---|---|---|---|---|---|
| CL-00 | `feat/cl-00-compatibility-contracts` | `3ad5bb6bd3f76f6879d84b78ea39edd3e01ec296` | `c014464237fd3c95bda08bc18bfab8ba8f532308` | [#1286](https://github.com/lidge-jun/opencodex/pull/1286) | ACCEPTED AFTER CODERABBIT REMEDIATION (merged to `dev` at `243c3f4905797aa11c62ba933bb03d6d721266fd`) |
| CL-01 | `feat/cl-01-conformance-harness` | `c2113ca47b8a05c5a5f90679e4eaa640ca2c6a66` | `22d608c82d82e2746c0cef9cd761db19a8e465ee` | [draft Wibias #10](https://github.com/Wibias/opencodex/pull/10) | ACCEPTED (contract-corrected revalidation) |
| CL-01 | `feat/cl-01-conformance-harness` | `c2113ca47b8a05c5a5f90679e4eaa640ca2c6a66` | `22d608c82d82e2746c0cef9cd761db19a8e465ee` | [#1320](https://github.com/lidge-jun/opencodex/pull/1320) | MERGED TO `dev` at `4bb249b756abd468c675d2d92fffe4da95ad3e2a` |
| CL-02 | `feat/cl-02-evidence-ledger` | `4bb249b756abd468c675d2d92fffe4da95ad3e2a` | (phase-2 review fixes in progress) | [draft #1333](https://github.com/lidge-jun/opencodex/pull/1333) | IMPLEMENTATION COMPLETE — PHASE-2 REVIEW FIXES — NOT INDEPENDENTLY ACCEPTED |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Pin the current unaccepted CL-02 revision.

Line 24 uses the Accepted head column for a status string. Lines 125-138 record the prior accepted SHA but not the SHA for the current CodeRabbit remediation. A draft PR can move, so this document cannot identify the exact revision covered by the stated validation and pending acceptance.

Keep Accepted head as until independent acceptance. Add a Current candidate head entry with the remediation commit SHA. Reference the same SHA in the current validation status.

Proposed wording
-| CL-02 | `feat/cl-02-evidence-ledger` | `4bb249b756abd468c675d2d92fffe4da95ad3e2a` | (phase-2 review fixes in progress) | [draft `#1333`](https://github.com/lidge-jun/opencodex/pull/1333) | IMPLEMENTATION COMPLETE — PHASE-2 REVIEW FIXES — NOT INDEPENDENTLY ACCEPTED |
+| CL-02 | `feat/cl-02-evidence-ledger` | `4bb249b756abd468c675d2d92fffe4da95ad3e2a` | — | [draft `#1333`](https://github.com/lidge-jun/opencodex/pull/1333) | IMPLEMENTATION COMPLETE — PHASE-2 REVIEW FIXES — NOT INDEPENDENTLY ACCEPTED |
...
+- **Current candidate head:** `<current CL-02 remediation SHA>`
- **Current CodeRabbit remediation:** committed on draft PR `#1333`; current CI and review reconciliation are required before this head may be recorded as accepted.
+- **Current CodeRabbit remediation:** committed at `<current CL-02 remediation SHA>` on draft PR `#1333`; current CI and review reconciliation are required before this head may be recorded as accepted.

Also applies to: 123-140

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@devlog/_plan/260807_compatibility_lab/001_pr_stack_status.md` at line 24,
Update the CL-02 status entry to keep the Accepted head value as — until
independent acceptance. Add a Current candidate head field containing the
remediation commit SHA, and use that same SHA in the validation status recorded
in the CL-02 history section around the existing prior accepted SHA.

| CL-03 | — | — | — | — | NOT STARTED |

The CL-01 starting SHA is the exact CL-00 tip recorded when CL-01 began. Its
moving base-ref name is not a substitute for that historical SHA.

CL-02 starts from the exact CL-01 merge commit on `dev`
(`4bb249b756abd468c675d2d92fffe4da95ad3e2a` / upstream #1320). It does **not**
base on the pre-merge CL-01 feature branch tip.

## CL-00 acceptance log

- Live-tree audit covered provider registry/derivation, Routing Profiles,
Expand Down Expand Up @@ -62,6 +68,7 @@ Independent CL-00 acceptance review is frozen at
- **CL-00 merge base on `dev`:** `243c3f4905797aa11c62ba933bb03d6d721266fd`
- **Post-rebase harness commit:** `cfe27b0dcb26a1bf0bb56f68f952e6e4f4d80fe9` (rebase-only)
- **Correction head:** `574f1d5eb93c091494549ffc0e26ea7a4879c12c` (implementation); **tip:** `22d608c82d82e2746c0cef9cd761db19a8e465ee`
- **Merged to `dev`:** `4bb249b756abd468c675d2d92fffe4da95ad3e2a` via upstream [#1320](https://github.com/lidge-jun/opencodex/pull/1320).

### Corrections applied

Expand All @@ -88,8 +95,52 @@ Independent CL-00 acceptance review is frozen at
- None for CL-01 correction.
- Full-suite green remains unavailable on this host for documented Windows/Bun reasons.

## CL-02 implementation log

- **Branch:** `feat/cl-02-evidence-ledger`
- **Starting/base SHA:** `4bb249b756abd468c675d2d92fffe4da95ad3e2a` (CL-01 merge via #1320)
- **Scope:** append-only JSONL evidence ledger with an explicit sensitive-purge
exception: when the `ledger` purge action is requested, targeted evidence is
physically removed by atomic ledger rewrite and a `purge_tombstone` remains as
the auditable record; SQLite is rebuilt from the rewritten ledger and retained
content-addressed artifacts. The phase also includes the content-addressed
artifact store, disposable/rebuildable SQLite projection, ClaimSourceManifestV1,
invalidation semantics, and the CL-01 → observation persistence seam.
- **Explicitly out of scope:** CL-03 live probes, CL-04 CLI/API, CL-05 UI,
CL-06 profile fields, Fabric, shadow workflows.

### Boundary note (verdict algorithm)

CL-02 implements frozen `all-applicable-required-pass-v1` evaluation with
subject-aware applicability (required scenarios whose manifest requirements
match the exact protocol subject, excluding live-reserved cases in fixture
mode). Positive `VERIFIED` requires a non-empty applicable required set and a
current pass for every member. Descriptor/handle-bound artifact I/O, fail-closed
sensitive purge with shared-artifact retention, recursive event admission
ceilings, and unusable-evidence exclusion from projection are implemented.
Claims cannot produce `PROBED`/`VERIFIED`.

### CL-02 validation status (2026-08-09 phase-2 review fixes)

- **Prior accepted review-fix head:** `cf626d14c823413fbcd6ac2625d1da16bbac714e`
- **Phase-2 scope:** eleven independent-review blockers (artifact dirfd I/O,
purge scratch/export + explicit sensitive artifacts, streaming JSONL replay,
zero-applicable UNKNOWN, `newest-required-observation-v1`, multi-surface
applicability, historical manifest no-substitution, closed event admission,
corrupt superseding claims, ArtifactStore lifecycle, frozen behaviour
fingerprint).
- **Previous local validation:** `bun x tsc --noEmit`, `bun run privacy:scan`,
`tests/lab-evidence-ledger.test.ts` (41/41), `tests/lab-conformance-harness.test.ts`
(17/17), `tests/repo-hygiene.test.ts` (11/11), `git diff --check` green on
Windows host before the current CodeRabbit remediation pass.
- **Current CodeRabbit remediation:** committed on draft PR #1333; current CI and
review reconciliation are required before this head may be recorded as accepted.
- **Independent acceptance:** not yet — draft PR #1333 remains open for review.
- **CL-03:** not started.

## Authorization

- CL-00: **ACCEPTED** (merged #1286).
- CL-01: **ACCEPTED (contract-corrected revalidation)** — ready for stack review against `dev`.
- CL-02: **NOT STARTED / NOT AUTHORIZED**.
- CL-01: **MERGED** via #1320 at `4bb249b756abd468c675d2d92fffe4da95ad3e2a`.
- CL-02: **IMPLEMENTATION COMPLETE — PHASE-2 REVIEW FIXES — NOT INDEPENDENTLY ACCEPTED** on `feat/cl-02-evidence-ledger` (draft #1333).
- CL-03: **NOT STARTED**.
114 changes: 114 additions & 0 deletions src/lab/artifacts/sanitize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Deny-by-default sanitization before artifact hashing/writing.
* Never persists prompts, secrets, paths, account IDs, raw URLs, or provider bodies.
*/
import type { ArtifactClass } from "../constants";
import { MAX_SANITIZED_STRING_FIELD } from "../constants";
import { jcsStringify } from "../digest";
import { redactSecretString } from "../../lib/redact";

const FORBIDDEN_KEY = /^(?:authorization|proxy-authorization|cookie|set-cookie|api[-_]?key|x-api-key|token|secret|password|email|prompt|messages|content|body|url|hostname|baseUrl|path|account|alias)$/i;
const SECRETISH = /sk-[a-z0-9]{10,}|Bearer\s+[A-Za-z0-9._\-]+|ghp_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}/i;
Comment thread
Wibias marked this conversation as resolved.
const SECRETISH_GLOBAL = /sk-[a-z0-9]{10,}|Bearer\s+[A-Za-z0-9._\-]+|ghp_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}/gi;

export function redactForArtifact(artifactClass: ArtifactClass, payload: unknown): unknown {
if (
artifactClass === "fixture" ||
artifactClass === "scenario_manifest" ||
artifactClass === "suite_manifest" ||
artifactClass === "claim_source_manifest"
) {
// Contract artifacts are already synthetic/canonical. Mutating them would
// invalidate content-addressed digests; reject secret-shaped material instead.
assertNoSecretMaterial(payload, 0);
return payload;
}
return scrubValue(payload, 0);
}

const FORBIDDEN_CONTRACT_KEYS = /^(?:authorization|proxy-authorization|cookie|set-cookie|api[-_]?key|x-api-key|token|secret|password|email|prompt|messages|baseUrl|hostname|account|alias)$/i;

function assertNoSecretMaterial(value: unknown, depth: number): void {
if (depth > 8) {
throw new Error("contract artifact exceeds sanitization inspection depth");
}
if (typeof value === "string") {
if (SECRETISH.test(value)) {
throw new Error("contract artifact contains forbidden secret-shaped material");
}
return;
}
if (Array.isArray(value)) {
for (const item of value) assertNoSecretMaterial(item, depth + 1);
return;
}
if (value && typeof value === "object") {
for (const [key, child] of Object.entries(value as object)) {
if (FORBIDDEN_CONTRACT_KEYS.test(key)) {
throw new Error(`contract artifact forbids key ${key}`);
}
assertNoSecretMaterial(child, depth + 1);
}
}
}

function scrubValue(value: unknown, depth: number): unknown {
if (depth > 8) return "[truncated_depth]";
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
if (typeof value === "string") return scrubString(value);
if (value instanceof Uint8Array) {
const text = new TextDecoder().decode(value);
return new TextEncoder().encode(scrubString(text));
}
if (Array.isArray(value)) {
if (value.length > 256) return value.slice(0, 256).map((v) => scrubValue(v, depth + 1));
return value.map((v) => scrubValue(v, depth + 1));
}
if (typeof value === "object") {
const out: Record<string, unknown> = {};
const keys = Object.keys(value as object).slice(0, 64);
for (const key of keys) {
if (FORBIDDEN_KEY.test(key)) {
out[key] = "[redacted]";
continue;
}
out[key] = scrubValue((value as Record<string, unknown>)[key], depth + 1);
}
return out;
}
return "[unsupported]";
}

function scrubString(value: string): string {
let s = redactSecretString(value);
s = s.replace(SECRETISH_GLOBAL, "[REDACTED]");
// Strip absolute filesystem paths (coarse)
s = s.replace(/(?:[A-Za-z]:\\|\/(?:home|Users|tmp|var|etc|root|mnt)\/)[^\s"']+/g, "[path]");
// Strip URL userinfo / private hosts roughly
s = s.replace(/https?:\/\/[^\s"']+/gi, (url) => {
try {
const u = new URL(url);
if (u.username || u.password) return "[redacted-url]";
if (/^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[0-1])\.)/i.test(u.hostname)) {
return `${u.protocol}//[private-host]${u.pathname}`;
}
return `${u.protocol}//[host]${u.pathname}`;
} catch {
return "[redacted-url]";
}
});
const bytes = new TextEncoder().encode(s);
if (bytes.byteLength > MAX_SANITIZED_STRING_FIELD) {
return new TextDecoder().decode(bytes.slice(0, MAX_SANITIZED_STRING_FIELD));
}
return s;
}

/** Stable privacy boundary for diagnostic text that may be persisted. */
export function sanitizeDiagnostic(value: unknown): string {
return scrubString(value instanceof Error ? value.message : String(value));
}

export function sanitizedJsonBytes(value: unknown): Uint8Array {
return new TextEncoder().encode(jcsStringify(scrubValue(value, 0)));
}
Loading
Loading