diff --git a/package.json b/package.json index 8c39dc6..683ac69 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ "scripts": { "demo": "tsx scripts/demo.ts", "dev": "pnpm --parallel --filter @keyring/server --filter @keyring/web dev", - "build": "pnpm -r run build", + "build": "tsc -b --pretty false && pnpm --filter @keyring/web run build", "test": "vitest run", "test:watch": "vitest", "lint": "eslint .", "lint:fix": "eslint . --fix", - "typecheck": "pnpm -r run typecheck", + "typecheck": "tsc -b --pretty false && pnpm --filter @keyring/web exec tsc -p tsconfig.json --noEmit", "format": "prettier --write .", "format:check": "prettier --check .", "db:migrate": "pnpm --filter @keyring/server db:migrate", diff --git a/packages/connectors/package.json b/packages/connectors/package.json index 7d5cf6f..dddbd57 100644 --- a/packages/connectors/package.json +++ b/packages/connectors/package.json @@ -13,8 +13,8 @@ "types": "./dist/index.d.ts", "files": ["dist", "fixtures"], "scripts": { - "build": "tsc -p tsconfig.json", - "typecheck": "tsc -p tsconfig.json --noEmit" + "build": "tsc -b tsconfig.json --pretty false", + "typecheck": "tsc -b tsconfig.json --pretty false" }, "dependencies": { "@keyring/core": "workspace:*" diff --git a/packages/connectors/src/github/connector.ts b/packages/connectors/src/github/connector.ts index be71181..93a8777 100644 --- a/packages/connectors/src/github/connector.ts +++ b/packages/connectors/src/github/connector.ts @@ -432,12 +432,14 @@ export function createGitHubConnector(options: GitHubConnectorOptions): Connecto return { ok: false, error: "not a github grant" }; } - const login = grant.principal.identifiers.find((i) => i.kind === "username")?.value; + const login = grant.principal.identifiers.find( + (i: Identifier) => i.kind === "username", + )?.value; const resourceId = String(grant.resource.id); const teamMatch = resourceId.match(/^([^/]+)\/team:(.+)$/); const patMatch = resourceId.match(/^([^/]+)\/pat:(.+)$/); const deployKeyId = grant.principal.identifiers.find( - (i) => i.kind === "key_id" && i.source === "github_deploy_keys", + (i: Identifier) => i.kind === "key_id" && i.source === "github_deploy_keys", )?.value; // --- Team membership --- diff --git a/packages/connectors/src/google-workspace/connector.ts b/packages/connectors/src/google-workspace/connector.ts index ec795c9..8fbe02e 100644 --- a/packages/connectors/src/google-workspace/connector.ts +++ b/packages/connectors/src/google-workspace/connector.ts @@ -1,4 +1,4 @@ -import { createGrant, type Grant } from "@keyring/core"; +import { createGrant, type Grant, type Identifier } from "@keyring/core"; import { asArray, asObject, callJson, paginateTokens } from "../mcp/paginate.js"; import { McpToolError, type McpToolCaller } from "../mcp/types.js"; @@ -374,7 +374,8 @@ export function createGoogleWorkspaceConnector( return { ok: false, error: "not a google_workspace grant" }; } const email = grant.principal.identifiers.find( - (i) => i.kind === "work_email" || i.kind === "personal_email", + (i: Identifier) => + i.kind === "work_email" || i.kind === "personal_email", )?.value; if (!email) return { ok: false, error: "missing email identifier" }; diff --git a/packages/connectors/tsconfig.json b/packages/connectors/tsconfig.json index 7b02c75..4cf982e 100644 --- a/packages/connectors/tsconfig.json +++ b/packages/connectors/tsconfig.json @@ -1,9 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "composite": true, "outDir": "dist", "rootDir": "src" }, "include": ["src/**/*"], - "exclude": ["src/**/*.test.ts"] + "exclude": ["src/**/*.test.ts"], + "references": [{ "path": "../core" }] } diff --git a/packages/core/package.json b/packages/core/package.json index 5e275cd..244dde7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,8 +13,8 @@ "types": "./dist/index.d.ts", "files": ["dist"], "scripts": { - "build": "tsc -p tsconfig.json", - "typecheck": "tsc -p tsconfig.json --noEmit", + "build": "tsc -b tsconfig.json --pretty false", + "typecheck": "tsc -b tsconfig.json --pretty false", "reconcile": "node dist/identity/cli.js" }, "bin": { diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 7b02c75..9889060 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "composite": true, "outDir": "dist", "rootDir": "src" }, diff --git a/packages/server/package.json b/packages/server/package.json index 8e01843..6a992a1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,8 +21,8 @@ ], "scripts": { "dev": "tsx watch src/index.ts", - "build": "tsc -p tsconfig.json", - "typecheck": "tsc -p tsconfig.json --noEmit", + "build": "tsc -b tsconfig.json --pretty false", + "typecheck": "tsc -b tsconfig.json --pretty false", "start": "node dist/index.js", "db:generate": "drizzle-kit generate", "db:migrate": "tsx src/db/migrate-cli.ts" diff --git a/packages/server/src/db/audit-append-only.test.ts b/packages/server/src/db/audit-append-only.test.ts index b0019a4..cc6dcc7 100644 --- a/packages/server/src/db/audit-append-only.test.ts +++ b/packages/server/src/db/audit-append-only.test.ts @@ -2,7 +2,7 @@ import { appendAuditRecord, asApprovalCardId, } from "@keyring/core"; -import { sql } from "drizzle-orm"; +import { inArray, sql } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import type { AppDb } from "./client.js"; @@ -110,4 +110,58 @@ describe("audit_records append-only", () => { expect(after.ok).toBe(true); expect(after.count).toBe(before.count + 10); }); + + it("restores the timestamp default after concurrent identical-time appends", async () => { + const recordedAt = "2026-08-29T23:00:00.000Z"; + + try { + await db.execute( + sql`ALTER TABLE audit_records ALTER COLUMN recorded_at SET DEFAULT '2026-08-29T23:00:00.000Z'::timestamptz`, + ); + + const appended = await Promise.all( + Array.from({ length: 20 }, (_, i) => + appendChainedAudit(db, { + cardId: asApprovalCardId( + `card-identical-recorded-at-${i}-${crypto.randomUUID()}`, + ), + action: "approve", + approvedBy: "judge@acme.com", + approvedAt: new Date(recordedAt), + executedAt: new Date(recordedAt), + result: "success", + evidenceSnapshot: evidence, + }), + ), + ); + + const appendedRows = await db + .select({ + id: auditRecords.id, + recordedAt: auditRecords.recordedAt, + }) + .from(auditRecords) + .where( + inArray( + auditRecords.id, + appended.map((record) => record.id), + ), + ); + expect(appendedRows).toHaveLength(20); + expect( + new Set(appendedRows.map((row) => row.recordedAt.toISOString())), + ).toEqual(new Set([recordedAt])); + + const allRows = await db + .select({ prevHash: auditRecords.prevHash }) + .from(auditRecords); + const prevHashes = allRows.map((row) => row.prevHash); + expect(new Set(prevHashes).size).toBe(prevHashes.length); + expect((await verifyStoredAuditChain(db)).ok).toBe(true); + } finally { + await db.execute( + sql`ALTER TABLE audit_records ALTER COLUMN recorded_at SET DEFAULT now()`, + ); + } + }); }); diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 7b02c75..4f1515e 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,9 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "composite": true, "outDir": "dist", "rootDir": "src" }, "include": ["src/**/*"], - "exclude": ["src/**/*.test.ts"] + "exclude": ["src/**/*.test.ts"], + "references": [{ "path": "../core" }, { "path": "../connectors" }] } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2b884fa --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "files": [], + "references": [ + { "path": "./packages/core" }, + { "path": "./packages/connectors" }, + { "path": "./packages/server" } + ] +}