Delete 38 assertions that were never necessary, and the bug one was hiding

I expected the 35 remaining findings to need a VaultIndex remodel. They did
not. `vaultIndexSchema` and every entry schema are `.loose()`, which already
gives both types an index signature — so `index["_system"]`, `entry["label"]`
and assignment to `Record<string, unknown>` all compile with no cast at all. I
checked before rewriting anything, and the answer was that 38 of the 44 were
simply redundant, copied from each other rather than needed.

That is the linter's real value here: it did not find a design flaw, it made
me test an assumption I would otherwise have acted on. The remodel would have
been work done to satisfy a belief that was false.

Removing them let the compiler see three things the casts had been hiding:

- `nextTotpIndex` filtered on `type === "totp" || kind === "totp"`, and TS
  reported the second half as unreachable. It is: withKind() populates kind
  from type at parse time and the union is discriminated on kind, so the
  fallback could never fire. Now filters on the discriminant alone.
- `ensureIndex0Payload` always ran normalizeIndex0 and then returned `Dict`,
  throwing the guarantee away, so all three callers asserted it back with
  `as unknown as Index0`. It now returns an `Index0Payload` that carries what
  the function established, built in that shape rather than asserted into it,
  and the type propagates through compactIndex0Payload and
  rebuildCanonicalViewsPayload.
- `sign()` in recoveryDrills declared a DrillRecord parameter it never used
  as one — it HMACs a canonical serialization — which forced the verifier to
  assert a shape it had not established while checking a signature.

Also `rebuildIndex0Checkpoints` typed to the one field it reads, and
console.ts asking `"isTTY" in this.out` rather than asserting a shape onto a
WritableStream.

One assertion remains, in entrySchema, and it is structural rather than
evidential: z.preprocess types its output from an inner union deliberately
wider than Entry, and no zod spelling expresses "parses to a superset, hands
back the narrow type". Disabled inline with that written down.

44 -> 0. The lint step is now in ci_prepush.sh, and the gate runs green in
44.5 seconds. 689 tests still pass.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
thePR0M3TH3ANandClaude Opus 5 committed 2026-08-20 13:40:42 -04:00
1 parent e9d49d10bc
commit 72b3e52f50
11 files changed
+101 -58

No files matched your search

+8 -8
View File
@@ -205,11 +205,11 @@ async function mutate<T>(
try {
const updated = emitEntryEvents(vault.index, {
before,
after: vault.index.entries as unknown as Record<string, unknown>,
after: vault.index.entries,
fingerprintDir: profileDir(ctx),
now: Math.floor(ctx.now() / 1000),
});
(vault.index as unknown as Record<string, unknown>)["_system"] = updated["_system"];
(vault.index)["_system"] = updated["_system"];
} catch {
// Derived state; never fail a committed write over it.
}
@@ -284,7 +284,7 @@ export function registerRoutes(server: ApiServer, ctx: ApiContext): void {
const rows = Object.entries(vault.index.entries)
.map(([id, entry]) => ({ id, entry: entry as Entry }))
.filter(({ entry }) => {
const e = entry as unknown as Record<string, unknown>;
const e = entry;
if (!includeArchived && e["archived"] === true) return false;
if (kindFilter && String(e["kind"] ?? e["type"] ?? "") !== kindFilter) return false;
if (!query) return true;
@@ -631,9 +631,9 @@ export function registerRoutes(server: ApiServer, ctx: ApiContext): void {
const vault = await readVault(ctx);
const now = Math.floor(ctx.now() / 1000);
const codes = Object.entries(vault.index.entries)
.filter(([, e]) => String((e as unknown as Record<string, unknown>)["kind"] ?? "") === "totp")
.filter(([, e]) => String((e)["kind"] ?? "") === "totp")
.map(([id, e]) => {
const entry = e as unknown as Record<string, unknown>;
const entry = e;
const secret = materializeSecret(vault.index, id, e as Entry, mnemonic, {
timestamp: now,
});
@@ -660,9 +660,9 @@ export function registerRoutes(server: ApiServer, ctx: ApiContext): void {
// Every TOTP secret in the vault, in plaintext. Password-gated for
// that reason alone.
const entries = Object.entries(vault.index.entries)
.filter(([, e]) => String((e as unknown as Record<string, unknown>)["kind"] ?? "") === "totp")
.filter(([, e]) => String((e)["kind"] ?? "") === "totp")
.map(([, e]) => {
const entry = e as unknown as Record<string, unknown>;
const entry = e;
// The SECRET, not a code. materializeSecret returns the current
// 6-digit code for a totp entry, which would make this export look
// correct and be useless — an authenticator cannot be seeded from
@@ -894,7 +894,7 @@ export function registerRoutes(server: ApiServer, ctx: ApiContext): void {
const counts: Record<string, number> = {};
let archived = 0;
for (const entry of Object.values(vault.index.entries)) {
const e = entry as unknown as Record<string, unknown>;
const e = entry;
const kind = String(e["kind"] ?? e["type"] ?? "unknown");
counts[kind] = (counts[kind] ?? 0) + 1;
if (e["archived"] === true) archived++;
+12 -10
View File
@@ -417,11 +417,11 @@ function recordIndex0(
try {
const updated = emitEntryEvents(vault.index, {
before,
after: vault.index.entries as unknown as Record<string, unknown>,
after: vault.index.entries,
fingerprintDir,
now: Math.floor(Date.now() / 1000),
});
(vault.index as unknown as Record<string, unknown>)["_system"] = updated["_system"];
(vault.index)["_system"] = updated["_system"];
} catch {
// Deliberately swallowed; see the note above.
}
@@ -476,7 +476,7 @@ async function openReadAccess(opts: GlobalOpts): Promise<ReadAccess> {
* it needs the second factor to be live in the agent.
*/
const hydrate = async (id: string, entry: Entry): Promise<Entry> => {
const record = entry as unknown as Record<string, unknown>;
const record = entry;
if (!isPartitionStub(record)) return entry;
const client = new AgentClient(agentSocketPath(app.root));
const fp = await currentFingerprint(app, opts);
@@ -1478,10 +1478,7 @@ export function buildProgram(io: ProgramIo = defaultIo): Command {
let local: Record<string, unknown> | null = null;
if (localExists) {
try {
local = (await openVault(vaultPath, mnemonic)).index as unknown as Record<
string,
unknown
>;
local = (await openVault(vaultPath, mnemonic)).index;
} catch {
local = null; // unreadable local vault: treat the restore as recovery
}
@@ -2026,12 +2023,17 @@ export function buildProgram(io: ProgramIo = defaultIo): Command {
await mutateVault(opts, async (vault) => {
const partition = await readPartition(profileDir, tag);
for (const [id, raw] of Object.entries(vault.index.entries)) {
const entry = raw as unknown as Record<string, unknown>;
const entry = raw;
if (isPartitionStub(entry)) continue; // already moved
const kind = String(entry["kind"] ?? entry["type"] ?? "").toLowerCase();
if (!kinds.has(kind)) continue;
partition[id] = entry;
(vault.index.entries as unknown as Record<string, unknown>)[id] =
// Same single assertion as entryOps.insert, for the same reason:
// partitionStub builds a valid stub, but `kind` is a runtime string
// and the entry union is keyed on literals, so the compiler cannot
// see the two line up. Validated on the way back out by
// parsePartitionRecord.
(vault.index.entries as Record<string, Record<string, unknown>>)[id] =
partitionStub(id, entry, kind, Date.now() / 1000);
moved.push(id);
}
@@ -2487,7 +2489,7 @@ export function buildProgram(io: ProgramIo = defaultIo): Command {
const byKind: Record<string, number> = {};
let archived = 0;
for (const entry of Object.values(vault.index.entries)) {
const e = entry as unknown as Record<string, unknown>;
const e = entry;
const kind = String(e["kind"] ?? e["type"] ?? "unknown");
byKind[kind] = (byKind[kind] ?? 0) + 1;
if (e["archived"] === true) archived++;
+11 -2
View File
@@ -73,7 +73,16 @@ function canonical(value: unknown): string {
.join(",")}}`;
}
function sign(key: Uint8Array, previousSig: string, record: DrillRecord): string {
// Takes any record: it HMACs a canonical serialization, so the shape is not
// something it needs or checks. Verification re-signs a parsed line and
// compares — a wrong shape simply fails to match, which is reported as
// sig_mismatch. Declaring DrillRecord here made the verifier assert a shape
// it had not established, to satisfy a parameter that never used it.
function sign(
key: Uint8Array,
previousSig: string,
record: DrillRecord | Record<string, unknown>,
): string {
return hmacSha256Hex(key, utf8(`${previousSig}${canonical(record)}`));
}
@@ -209,7 +218,7 @@ export async function verifyRecoveryDrills(
continue;
}
delete body["sig"];
if (sign(key, previousSig, body as unknown as DrillRecord) !== sig) {
if (sign(key, previousSig, body) !== sig) {
errors.push(`sig_mismatch_line:${index}`);
}
// Chain forward on the RECORDED signature, not the computed one: a single
+3 -1
View File
@@ -53,7 +53,9 @@ export class ConsoleUi implements Ui {
}
clear(): void {
if ((this.out as unknown as { isTTY?: boolean }).isTTY) {
// `in` narrows instead of asserting: NodeJS.WritableStream has no isTTY,
// but the process streams do, and this asks rather than declares.
if ("isTTY" in this.out && this.out.isTTY) {
// Clear and reset the cursor, then clear scrollback: menus redraw
// constantly, and without the third sequence a session leaves hundreds
// of stale screens behind it.
+5 -5
View File
@@ -123,7 +123,7 @@ function title(s: Session, breadcrumb: string): void {
}
function kindOf(entry: Entry): string {
const e = entry as unknown as Record<string, unknown>;
const e = entry;
return String(e["kind"] ?? e["type"] ?? "unknown");
}
@@ -134,7 +134,7 @@ function kindOf(entry: Entry): string {
* seed at the entry's index — the same secret the code is computed from.
*/
function totpUri(entry: Entry, mnemonic: string): string {
const e = entry as unknown as Record<string, unknown>;
const e = entry;
const secret =
typeof e["secret"] === "string" && e["secret"]
? (e["secret"] as string)
@@ -174,11 +174,11 @@ async function mutate(s: Session, fn: (vault: OpenedVault) => void): Promise<voi
try {
const updated = emitEntryEvents(fresh.index, {
before,
after: fresh.index.entries as unknown as Record<string, unknown>,
after: fresh.index.entries,
fingerprintDir: s.app.profileDir(s.fingerprint),
now: Math.floor(s.clock() / 1000),
});
(fresh.index as unknown as Record<string, unknown>)["_system"] = updated["_system"];
(fresh.index)["_system"] = updated["_system"];
} catch {
// Derived state; never fail a committed write over it.
}
@@ -1576,7 +1576,7 @@ async function exportTotpCodes(s: Session): Promise<void> {
({ entry }) => kindOf(entry) === "totp" && !isArchived(entry),
);
const uris = rows.map(({ entry }) => ({
label: String((entry as unknown as Record<string, unknown>)["label"] ?? ""),
label: String((entry)["label"] ?? ""),
uri: totpUri(entry, s.vault.mnemonic),
}));
// This file is every 2FA secret in the vault, in plaintext.
+12
View File
@@ -250,6 +250,18 @@ export const partitionStubSchema = z
})
.loose();
// The one assertion chain left in the codebase, and it is structural rather
// than evidential. `z.preprocess` types its output from the inner union,
// which here is `partitionStub | knownKinds | unknownKind` — a wider type
// than `Entry` by construction, because a stub and an unknown-kind record are
// both legitimate index members that no typed operation should touch. Every
// consumer wants `Entry`. There is no zod spelling that expresses "parses to
// a superset, hands back the narrow type" without an assertion.
//
// It is safe in the way the rule cares about: nothing is being CLAIMED about
// unvalidated data. The value has just been parsed, one line above, by the
// schema being cast.
// oxlint-disable-next-line anti-slop/no-chained-type-assertions
export const entrySchema = z.preprocess(
withKind,
// The stub comes first: it is the narrower shape, and a stub of a known
@@ -73,7 +73,7 @@ export function findDerivationCollisions(index: VaultIndex): DerivationCollision
const byIndex = new Map<number, Array<{ id: string; kind: App32Kind; label: string }>>();
for (const [id, raw] of Object.entries(index.entries)) {
const entry = raw as unknown as Record<string, unknown>;
const entry = raw;
const kind = String(entry["kind"] ?? entry["type"] ?? "");
if (!(APP32_KINDS as readonly string[]).includes(kind)) continue;
const derivationIndex = derivationIndexOf(id, entry);
+2 -2
View File
@@ -96,7 +96,7 @@ const ALLOWED_FIELDS: Record<string, Set<string>> = {
};
function entryDict(index: VaultIndex, id: string): Dict {
const entry = (index.entries as unknown as Record<string, Dict>)[id];
const entry = (index.entries)[id];
if (typeof entry !== "object" || entry === null) {
throw new Error(`Entry not found: ${id}`);
}
@@ -211,7 +211,7 @@ export interface ResolvedLink extends EntryLink {
/** Links for an entry with resolved target metadata. */
export function getLinks(index: VaultIndex, id: string): ResolvedLink[] {
const src = entryDict(index, id);
const entries = index.entries as unknown as Record<string, Dict>;
const entries = index.entries;
return normalizeLinks(src["links"] ?? []).map((link) => {
const target = entries[String(link.target_id)];
return {
+13 -6
View File
@@ -75,7 +75,7 @@ export function nextIndex(index: VaultIndex): number {
/** The persisted allocation watermark, 0 when absent (pre-watermark vault). */
function storedNextIndex(index: VaultIndex): number {
const meta = (index as unknown as Dict)["_sync_meta"];
const meta = (index)["_sync_meta"];
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return 0;
const raw = Number((meta as Dict)["next_index"]);
return Number.isSafeInteger(raw) && raw > 0 ? raw : 0;
@@ -83,7 +83,7 @@ function storedNextIndex(index: VaultIndex): number {
/** One past the highest tombstoned id. Non-numeric tombstone keys are historical junk and skipped. */
function tombstoneFloor(index: VaultIndex): number {
const meta = (index as unknown as Dict)["_sync_meta"];
const meta = (index)["_sync_meta"];
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return 0;
const tombstones = (meta as Dict)["tombstones"];
if (typeof tombstones !== "object" || tombstones === null || Array.isArray(tombstones)) return 0;
@@ -103,7 +103,7 @@ function tombstoneFloor(index: VaultIndex): number {
* preserve it without a schema bump.
*/
function bumpNextIndex(index: VaultIndex, id: number): void {
const container = index as unknown as Dict;
const container = index;
const meta =
typeof container["_sync_meta"] === "object" &&
container["_sync_meta"] !== null &&
@@ -116,8 +116,12 @@ function bumpNextIndex(index: VaultIndex, id: number): void {
/** Next TOTP derivation index: max over totp entries' index field + 1. */
export function nextTotpIndex(index: VaultIndex): number {
const indices = Object.values(index.entries as unknown as Record<string, Dict>)
.filter((e) => e["type"] === "totp" || e["kind"] === "totp")
const indices = Object.values(index.entries)
// `kind` alone, not `type || kind`. withKind() populates kind from type
// during parsing and the entry union is discriminated on it, so the
// second half of that condition could never be true — the compiler said
// so the moment the casts stopped blinding it.
.filter((e) => e["kind"] === "totp")
.map((e) => Math.trunc(Number(e["index"] ?? 0)));
return indices.length > 0 ? Math.max(...indices) + 1 : 0;
}
@@ -133,7 +137,10 @@ function insert(index: VaultIndex, id: number, entry: Dict): string {
if (Object.prototype.hasOwnProperty.call(index.entries, key)) {
throw new Error(`refusing to overwrite existing entry ${key}`);
}
(index.entries as unknown as Dict)[key] = entry;
// One direct assertion, not a chain: `insert` receives an entry the caller
// has just assembled field by field, and a literal's `type: "totp"` widens
// to string without a const assertion at every call site.
(index.entries as Record<string, Dict>)[key] = entry;
// Covers explicit-id inserts too: an entry created at #500 pushes the
// watermark past 500 even though nextIndex never saw it.
bumpNextIndex(index, id);
+31 -14
View File
@@ -444,13 +444,27 @@ export function deriveIndex0Context(
};
}
/**
* A payload whose `_system.index0` has been through normalizeIndex0.
*
* The type exists so the guarantee survives the return. ensureIndex0Payload
* always normalized, but declared `Dict`, so all three callers immediately
* asserted the knowledge back with `as unknown as Index0` — three chained
* assertions standing in for a fact the function had already established one
* line earlier.
*/
export type Index0Payload = Dict & { _system: Dict & { index0: Index0 } };
/** Ensure `_system.index0` exists and is normalized, without touching entries. */
export function ensureIndex0Payload(data: unknown): Dict {
const out = isDict(data) ? { ...data } : {};
const system = isDict(out["_system"]) ? { ...(out["_system"] as Dict) } : {};
system["index0"] = normalizeIndex0(system["index0"]);
out["_system"] = system;
return out;
export function ensureIndex0Payload(data: unknown): Index0Payload {
const base = isDict(data) ? { ...data } : {};
const system = isDict(base["_system"]) ? { ...(base["_system"] as Dict) } : {};
// Built in the shape it promises rather than assembled and then asserted,
// so nothing here has to be taken on trust.
return {
...base,
_system: { ...system, index0: normalizeIndex0(system["index0"]) },
};
}
// ------------------------------------------------------------------- events
@@ -528,7 +542,7 @@ export interface AppendEventOptions extends Omit<
*/
export function appendIndex0Event(payload: unknown, options: AppendEventOptions): Dict {
const out = ensureIndex0Payload(payload);
const system = (out["_system"] as Dict)["index0"] as unknown as Index0;
const system = out._system.index0;
const context = deriveIndex0Context(options.fingerprintDir, {
...(options.actorType !== undefined && { actorType: options.actorType }),
});
@@ -609,7 +623,10 @@ export function buildDailyCheckpoint(
}
export function rebuildIndex0Checkpoints(
index0: Dict,
// Reads only `events`, like recomputeIndex0Stats reads only three fields.
// Typed to that rather than to a bare Dict, so an Index0 goes in without a
// cast.
index0: Pick<Index0, "events">,
options: { maxCheckpointsPerWriter?: number } = {},
): Record<string, Dict> {
const maxPerWriter = options.maxCheckpointsPerWriter ?? INDEX0_MAX_CHECKPOINTS_PER_WRITER;
@@ -661,7 +678,7 @@ export function compactIndex0(
): Index0 {
const normalized = normalizeIndex0(index0);
normalized.checkpoints = rebuildIndex0Checkpoints(
normalized as unknown as Dict,
normalized,
options,
);
normalized.stats = recomputeIndex0Stats(normalized);
@@ -789,9 +806,9 @@ function buildRecentActivityView(
export function rebuildCanonicalViewsPayload(
payload: unknown,
options: { fingerprintDir?: string | null } = {},
): Dict {
): Index0Payload {
const out = ensureIndex0Payload(payload);
const index0 = (out["_system"] as Dict)["index0"] as unknown as Index0;
const index0 = out._system.index0;
const entries = normalizeMapping(out["entries"]);
const events = Object.values(index0.events)
.map((event) => normalizeIndex0Event(event))
@@ -864,9 +881,9 @@ export function rebuildCanonicalViewsPayload(
export function compactIndex0Payload(
payload: unknown,
options: { maxCheckpointsPerWriter?: number; fingerprintDir?: string | null } = {},
): Dict {
): Index0Payload {
const out = ensureIndex0Payload(payload);
(out["_system"] as Dict)["index0"] = compactIndex0((out["_system"] as Dict)["index0"], {
out._system.index0 = compactIndex0(out._system.index0, {
...(options.maxCheckpointsPerWriter !== undefined && {
maxCheckpointsPerWriter: options.maxCheckpointsPerWriter,
}),
@@ -885,7 +902,7 @@ export function buildManifestIndex0Metadata(
const compacted = compactIndex0Payload(payload, {
...(options.fingerprintDir !== undefined && { fingerprintDir: options.fingerprintDir }),
});
const index0 = (compacted["_system"] as Dict)["index0"] as unknown as Index0;
const index0 = compacted._system.index0;
const checkpoints = Object.values(index0.checkpoints).filter(isDict);
const selected = [...checkpoints]
.sort((a, b) => {
+3 -9
View File
@@ -13,7 +13,7 @@
# preflight 2.6s
# poetry check --lock 0.6s
# black --check 2.5s
# oxlint 0.9s (not enabled yet, see below)
# oxlint 0.9s
# tsc --noEmit (workspace) 7.3s
# pytest determinism subset 5.4s
# vitest core (node) 24.0s
@@ -50,14 +50,8 @@ poetry check --lock
step "python: formatting (the pinned black, not whatever is on PATH)"
poetry run black --check .
# NOT YET. `pnpm -C js lint` currently exits non-zero on 35 known chained type
# assertions -- one design problem (VaultIndex does not model what a vault
# index contains) counted 35 times. Putting a red check in the push gate is
# how a gate becomes something people route around, so it goes in when it goes
# green, not before. Track it with: pnpm -C js lint
#
# step "javascript: lint"
# pnpm -C js lint
step "javascript: lint"
pnpm -C js lint
step "javascript: typecheck"
pnpm -C js typecheck