Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9aed756
docs(ai): design 100% quality and governance gates
seonghobae Aug 4, 2026
7d8b5d6
docs(ai): plan complete assurance quality gates
seonghobae Aug 4, 2026
43d052f
test(ai): add complete production coverage gate
seonghobae Aug 4, 2026
d60e613
test(ai): enforce production JSDoc coverage
seonghobae Aug 4, 2026
d1f179b
test(ai): enable complete coverage and formatting gates
seonghobae Aug 4, 2026
6dc6bbe
ci(ai): diagnose complete quality gates
seonghobae Aug 4, 2026
dae22a4
build(ai): automate reviewed assurance repairs
seonghobae Aug 4, 2026
8cf45d4
ci(ai): apply reviewed assurance implementation
seonghobae Aug 4, 2026
c43335d
test(ai): retain detailed coverage evidence
seonghobae Aug 4, 2026
e005d11
ci(ai): retain detailed coverage artifact
seonghobae Aug 4, 2026
2d53f76
ci(ai): retain source-mapped coverage diagnostics
seonghobae Aug 4, 2026
2e92299
test(ai): add exhaustive coverage repair
seonghobae Aug 4, 2026
87fa7e8
ci(ai): execute exhaustive coverage repair
seonghobae Aug 4, 2026
41d521b
fix(ai): correct generated coverage test types
seonghobae Aug 4, 2026
af2f6cf
ci(ai): patch generated coverage evidence
seonghobae Aug 4, 2026
1a21fda
ci(ai): finalize verified assurance implementation
seonghobae Aug 4, 2026
e9df257
test(notification): stabilize expired lease fixture
seonghobae Aug 4, 2026
b6f2d31
ci(ai): stabilize notification lease evidence
seonghobae Aug 4, 2026
c152a9c
test(ai): pass disposable database boundary through Turborepo
seonghobae Aug 4, 2026
7febd0f
test(ai): enforce complete audit assurance gates
github-actions[bot] Aug 4, 2026
640821c
chore(web): discard generated build metadata
seonghobae Aug 4, 2026
4529986
chore(web): restore reviewed TypeScript configuration
seonghobae Aug 4, 2026
f809270
chore(web): remove generated TypeScript build state
seonghobae Aug 4, 2026
2cb5e6c
chore(repo): ignore generated TypeScript build state
seonghobae Aug 4, 2026
f3fd695
ci: enforce reproducible frozen dependency installs
seonghobae Aug 4, 2026
651cdd1
ci: repair AI quality gate dependencies
seonghobae Aug 4, 2026
61a345a
ci: expedite AI quality gate repair
seonghobae Aug 4, 2026
7e44284
ci: guarantee AI quality repair on pull request
seonghobae Aug 4, 2026
83c490f
fix(ai): repair quality gate evidence and dependencies
github-actions[bot] Aug 4, 2026
350312b
ci: let quality repair commit non-workflow files
seonghobae Aug 4, 2026
892aa85
ci: restore exact-head quality validation
seonghobae Aug 4, 2026
6a4d2c3
ci: format AI quality capability evidence
seonghobae Aug 4, 2026
4751dc5
style(ai): format quality capability evidence
github-actions[bot] Aug 4, 2026
c1182b6
ci: remove temporary AI quality format repair
seonghobae Aug 4, 2026
a8c9a6f
ci: temporarily repair AI quality formatting
seonghobae Aug 4, 2026
2b4dc23
style: format AI capability evidence
github-actions[bot] Aug 4, 2026
323d294
ci: retrigger exact-head validation
seonghobae Aug 4, 2026
5d9e30f
ci: finalize exact-head validation trigger
seonghobae Aug 4, 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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ jobs:
- name: Enable Corepack
run: corepack enable

- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Install reproducible dependencies
run: pnpm install --frozen-lockfile

- name: Check formatting
run: pnpm format:check
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ node_modules/
.next/
dist/
coverage/
*.tsbuildinfo
.env
.env.*
!.env.example
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to LifeOS are documented in this file.

### Added

- Executable AI-service JSDoc and exact 100% statement, branch, function, and line coverage gates, with an operator-facing governance assurance boundary.
- Tenant-safe durable planning search across goals, projects, and tasks, with Unicode-normalized exact, prefix, and whole-token matching.
- An authenticated same-origin planning-search boundary that signs the session-derived workspace context and forwards no browser credential to planning-service.
- An accessible quick-capture and search surface that keeps browser-local Today drafts visibly separate from durable workspace records.
Expand All @@ -18,6 +19,7 @@ All notable changes to LifeOS are documented in this file.

### Fixed

- Stale AI proposal revision conflicts now belong to the technology-independent audit domain while the PostgreSQL adapter preserves its compatibility export.
- Planning search now normalizes browser query text and prevents stale or unmounted requests from replacing the latest visible result state.
- Reminder fatigue deferral now crosses long IANA offset fallbacks and next-day quiet hours without abandoning the claimed occurrence.
- Notification workers now recover expired claims and exact delivery replays without creating duplicate inbox messages.
Expand Down
10 changes: 6 additions & 4 deletions apps/ai-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"private": true,
"scripts": {
"build": "nest build",
"dev": "nest start --watch",
"lint": "tsc --noEmit",
"test": "vitest run --passWithNoTests --no-file-parallelism",
"typecheck": "tsc --noEmit"
"dev": "nest start --watch --entryFile server",
"lint": "tsc --noEmit && prettier --single-quote --check package.json tsconfig.json vitest.config.ts \"src/**/*.ts\" ../../docs/operations/ai-proposal-audit-assurance.md ../../docs/superpowers/specs/2026-08-04-ai-service-quality-gates-design.md ../../docs/superpowers/plans/2026-08-04-ai-service-quality-gates.md",
"test": "vitest run --no-file-parallelism --coverage",
"typecheck": "tsc --noEmit",
"start": "node dist/server.js"
},
"dependencies": {
"@nestjs/common": "^11.1.6",
Expand All @@ -21,6 +22,7 @@
"@nestjs/cli": "^11.0.10",
"@types/node": "^24.3.0",
"@types/pg": "^8.20.0",
"@vitest/coverage-v8": "^3.2.4",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
}
Expand Down
3 changes: 3 additions & 0 deletions apps/ai-service/src/ai-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ProposalService, RuleBasedProposalModel } from './proposal-service';
const MAXIMUM_CONFIGURATION_LENGTH = 8 * 1024;
const databaseLogger = new Logger('AiDatabasePool');

/** Bounded environment values accepted by the AI production runtime. */
type RuntimeEnvironment = Readonly<Record<string, string | undefined>>;

/** Bounded PostgreSQL pool boundary used by the AI production runtime. */
Expand Down Expand Up @@ -44,6 +45,7 @@ export function createAiPoolErrorListener(

/** Adapts node-postgres to the minimal pool contract required by the AI service. */
class NodePostgresAiPool implements AiPool {
/** Creates the bounded adapter around one owned node-postgres pool. */
constructor(private readonly pool: Pool) {}

/** Executes SQL without exposing the wider node-postgres client surface. */
Expand All @@ -63,6 +65,7 @@ class NodePostgresAiPool implements AiPool {

/** Narrows the runtime pool to the repository's parameterized SQL contract. */
class NodePostgresProposalAuditSqlClient implements ProposalAuditSqlClient {
/** Creates the repository SQL adapter over the runtime-owned pool. */
constructor(private readonly pool: AiPool) {}

/** Delegates one parameterized query through the bounded pool interface. */
Expand Down
163 changes: 163 additions & 0 deletions apps/ai-service/src/docstring-coverage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { readdir, readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import * as ts from 'typescript';
import { describe, expect, it } from 'vitest';

/** One production declaration missing an explanatory JSDoc block. */
interface UndocumentedDeclaration {
readonly file: string;
readonly line: number;
readonly declaration: string;
}

/** Reports whether the declaration's leading trivia ends with one JSDoc block. */
function hasJSDoc(node: ts.Node, sourceFile: ts.SourceFile): boolean {
const leadingTrivia = sourceFile.text.slice(
node.getFullStart(),
node.getStart(sourceFile),
);
return /\/\*\*[\s\S]*?\*\/\s*$/u.test(leadingTrivia);
}

/** Maps a callable variable declaration to the owning statement carrying JSDoc. */
function documentationOwner(node: ts.Node): ts.Node {
if (
ts.isVariableDeclaration(node) &&
ts.isVariableDeclarationList(node.parent) &&
ts.isVariableStatement(node.parent.parent)
) {
return node.parent.parent;
}
return node;
}

/** Identifies callable variable or property initializers that require documentation. */
function hasCallableInitializer(
node: ts.VariableDeclaration | ts.PropertyDeclaration,
): boolean {
return (
node.initializer !== undefined &&
(ts.isArrowFunction(node.initializer) ||
ts.isFunctionExpression(node.initializer))
);
}

/** Produces a stable human-readable name for a documentation failure. */
function declarationName(node: ts.Node): string {
if (ts.isConstructorDeclaration(node)) {
const parent = node.parent;
return ts.isClassDeclaration(parent) && parent.name
? `${parent.name.text}.constructor`
: 'constructor';
}
if (
ts.isVariableDeclaration(node) ||
ts.isPropertyDeclaration(node) ||
ts.isFunctionDeclaration(node) ||
ts.isClassDeclaration(node) ||
ts.isInterfaceDeclaration(node) ||
ts.isTypeAliasDeclaration(node) ||
ts.isMethodDeclaration(node) ||
ts.isMethodSignature(node)
) {
return node.name?.getText() ?? node.kind.toString();
}
return node.kind.toString();
}

/** Identifies production declaration kinds that require explanatory JSDoc. */
function requiresJSDoc(node: ts.Node): boolean {
return (
ts.isFunctionDeclaration(node) ||
ts.isClassDeclaration(node) ||
ts.isInterfaceDeclaration(node) ||
ts.isTypeAliasDeclaration(node) ||
ts.isMethodDeclaration(node) ||
ts.isMethodSignature(node) ||
ts.isConstructorDeclaration(node) ||
(ts.isVariableDeclaration(node) && hasCallableInitializer(node)) ||
(ts.isPropertyDeclaration(node) && hasCallableInitializer(node))
);
}

/** Restricts the contract to top-level declarations and class/interface members. */
function isDocumentedScope(node: ts.Node, sourceFile: ts.SourceFile): boolean {
const owner = documentationOwner(node);
return (
owner.parent === sourceFile ||
ts.isClassDeclaration(node.parent) ||
ts.isInterfaceDeclaration(node.parent)
);
}

/** Collects every undocumented production declaration from one TypeScript source. */
function collectUndocumentedDeclarations(
file: string,
source: string,
): UndocumentedDeclaration[] {
const sourceFile = ts.createSourceFile(
file,
source,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS,
);
const missing: UndocumentedDeclaration[] = [];

/** Visits one syntax node and records any in-scope documentation failure. */
function visit(node: ts.Node): void {
if (
requiresJSDoc(node) &&
isDocumentedScope(node, sourceFile) &&
!hasJSDoc(documentationOwner(node), sourceFile)
) {
const position = sourceFile.getLineAndCharacterOfPosition(
node.getStart(sourceFile),
);
missing.push({
file,
line: position.line + 1,
declaration: declarationName(node),
});
}
ts.forEachChild(node, visit);
}

visit(sourceFile);
return missing;
}

/** Discovers production TypeScript sources while excluding all test fixtures. */
async function discoverSourceFiles(directory: string): Promise<string[]> {
const entries = await readdir(directory, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const path = resolve(directory, entry.name);
if (entry.isDirectory()) {
return await discoverSourceFiles(path);
}
return entry.isFile() &&
entry.name.endsWith('.ts') &&
!entry.name.endsWith('.test.ts')
? [path]
: [];
}),
);
return files.flat().sort();
}

describe('ai-service source documentation', () => {
it('documents every production declaration with JSDoc', async () => {
const sourceFiles = await discoverSourceFiles(__dirname);
const missing = (
await Promise.all(
sourceFiles.map(async (path) => {
const source = await readFile(path, 'utf8');
return collectUndocumentedDeclarations(path, source);
}),
)
).flat();

expect(missing).toEqual([]);
});
});
46 changes: 37 additions & 9 deletions apps/ai-service/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import {
type ProposalAuditRecord,
ProposalAuditValidationError,
ProposalDigestMismatchError,
type ProposalDecisionEvent,
} from './proposal-audit-domain';
import {
Expand All @@ -34,7 +35,6 @@ import {
import {
ProposalAuditPersistenceError,
ProposalDecisionConflictError,
ProposalDigestMismatchError,
} from './postgres-proposal-audit-repository';

/** Injection token for the narrowed inert proposal-generation contract. */
Expand All @@ -47,7 +47,7 @@ export const AI_RUNTIME = Symbol('AI_RUNTIME');
const auditLogger = new Logger('AiProposalAudit');

/** Narrow read-only proposal-generation contract exposed to the legacy controller. */
interface ProposalGenerator {
export interface ProposalGenerator {
/** Generates an inert proposal without executing any proposed operation. */
generateProposal(
workspaceId: string,
Expand Down Expand Up @@ -273,13 +273,41 @@ export class AiAppModule {}
})
export class AiProductionModule {}

/** Boots the production AI process with exactly-once shutdown hooks. */
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AiProductionModule);
app.enableShutdownHooks();
await app.listen(Number(process.env.AI_SERVICE_PORT ?? 4105), '0.0.0.0');
/** Minimal Nest application behavior needed by the AI process bootstrap. */
export interface AiBootstrapApplication {
/** Enables Nest-managed lifecycle shutdown hooks. */
enableShutdownHooks(): void;
/** Starts the HTTP server on one validated port and fixed host. */
listen(port: number, host: string): Promise<unknown>;
}

/** Factory used to construct the production Nest application. */
export type AiApplicationFactory = () => Promise<AiBootstrapApplication>;

/** Creates the production Nest application without starting its listener. */
export async function createAiApplication(): Promise<AiBootstrapApplication> {
return await NestFactory.create(AiProductionModule);
}

if (require.main === module) {
void bootstrap();
/** Parses the optional AI service port into the supported TCP range. */
export function resolveAiServicePort(value: string | undefined): number {
if (value === undefined || value.trim() === '') {
return 4_105;
}
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 65_535) {
throw new Error('AI service port is invalid');
}
return parsed;
}

/** Boots the production AI process with exactly-once shutdown hooks. */
export async function bootstrapAiService(
environment: Readonly<Record<string, string | undefined>> = process.env,
applicationFactory: AiApplicationFactory = createAiApplication,
): Promise<void> {
const port = resolveAiServicePort(environment.AI_SERVICE_PORT);
const app = await applicationFactory();
app.enableShutdownHooks();
await app.listen(port, '0.0.0.0');
}
Loading
Loading