Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ target frameworks when validating the full CI-equivalent test matrix.

For test suite structure, shared helpers, and test-writing conventions, see [TESTING_GUIDE.md](TESTING_GUIDE.md).

## CI / Artifact Distribution

Query commands accept `--read-only` (alias `--immutable`) to open an existing
CodeIndex database through SQLite's immutable read-only URI mode. Use this for
CI artifacts, mounted caches, and sandboxes where creating or updating
`codeindex.db-wal` / `codeindex.db-shm` sidecars is not allowed:

```bash
cdidx status --db /artifacts/codeindex.db --read-only --json
cdidx search AuthService --db /artifacts/codeindex.db --immutable
```

Mutating commands such as `index`, `backfill-fold`, `optimize`, and `vacuum`
require writable storage and reject read-only database opens.

## Filesystem Permissions

On POSIX filesystems, cdidx creates `.cdidx/` with mode `0700` and applies mode
`0600` to `codeindex.db` plus WAL/SHM sidecars when they exist. `status --json`
reports `data_dir_mode` and `db_file_mode` when the platform exposes Unix file
modes.

## Release Distribution Checklist

When preparing a release, verify every supported distribution channel documented
Expand Down
17 changes: 17 additions & 0 deletions changelog.d/unreleased/1730.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: added
issues:
- 1730
affected:
- src/CodeIndex/Cli/QueryCommandRunner.cs
- src/CodeIndex/Database/DbContext.cs
- DEVELOPER_GUIDE.md
---

## English

- **Added read-only database opens for query commands (#1730)** — query commands now accept `--read-only` / `--immutable` and translate normal database paths into SQLite immutable read-only URIs.

## 日本語

- **クエリコマンドで読み取り専用 DB オープンを追加しました (#1730)** — クエリコマンドは `--read-only` / `--immutable` を受け付け、通常の DB パスを SQLite の immutable read-only URI に変換します。
17 changes: 17 additions & 0 deletions changelog.d/unreleased/1794.security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: security
issues:
- 1794
affected:
- src/CodeIndex/Database/DbContext.cs
- src/CodeIndex/Models/QueryResults.cs
- DEVELOPER_GUIDE.md
---

## English

- **Restricted SQLite database file permissions on POSIX (#1794)** — cdidx now applies `0600` to `codeindex.db` and WAL/SHM sidecars and reports `db_file_mode` in `status --json`.

## 日本語

- **POSIX で SQLite DB ファイル権限を制限しました (#1794)** — cdidx は `codeindex.db` と WAL/SHM sidecar に `0600` を適用し、`status --json` に `db_file_mode` を出します。
18 changes: 18 additions & 0 deletions changelog.d/unreleased/1798.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
category: fixed
issues:
- 1798
affected:
- src/CodeIndex/Cli/IndexCommandRunner.cs
- src/CodeIndex/Database/DbContext.cs
- src/CodeIndex/Database/DbReader.FilesStatus.cs
- src/CodeIndex/Models/QueryResults.cs
---

## English

- **Checkpoint WAL before read-only fallback (#1798)** — writable-open fallback now attempts `wal_checkpoint(TRUNCATE)` first and exposes fallback/checkpoint diagnostics in `status --json`.

## 日本語

- **読み取り専用フォールバック前に WAL checkpoint を試みるようにしました (#1798)** — writable open のフォールバック前に `wal_checkpoint(TRUNCATE)` を試行し、fallback / checkpoint 診断を `status --json` に出します。
9 changes: 9 additions & 0 deletions src/CodeIndex/Cli/CliFlagSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ internal static class CliFlagSchema
"validate", "deps", "impact", "unused", "hotspots", "batch",
];

private static readonly string[] ReadOnlyDbCommands =
[
"search", "definition", "goto", "references", "callers", "callees",
"symbols", "files", "find", "excerpt", "map", "inspect", "outline", "status",
"validate", "deps", "impact", "unused", "hotspots",
];

private static readonly string[] JsonCommands =
[
"index", "backfill-fold", "optimize", "vacuum", "search", "definition", "goto", "references", "callers", "callees",
Expand Down Expand Up @@ -193,6 +200,8 @@ private static IReadOnlyList<CliFlag> BuildAll()
return new List<CliFlag>
{
new() { Name = "--db", ValuePlaceholder = "<path>", Description = "Database path", Commands = Set(DbPathCommands) },
new() { Name = "--read-only", Description = "Open the query database as immutable read-only storage", Commands = Set(ReadOnlyDbCommands) },
new() { Name = "--immutable", Description = "Alias for --read-only", Commands = Set(ReadOnlyDbCommands) },
new() { Name = "--workspace-db", ValuePlaceholder = "<path>", Description = "Additional workspace member database path for dependency aggregation", Commands = Set(WorkspaceDbCommands) },
new() { Name = "--data-dir", ValuePlaceholder = "<dir>", Description = "Directory containing codeindex.db; overrides CDIDX_DATA_DIR/XDG/workspace defaults", Commands = Set(DataDirCommands) },
new() { Name = "--json", Description = "JSON output; search also accepts --json=array for a single JSON array", Commands = Set(JsonCommands) },
Expand Down
8 changes: 8 additions & 0 deletions src/CodeIndex/Cli/IndexCommandRunner.Parse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static partial class IndexCommandRunner
"--parallelism",
"--commits", "--changed-between", "--files", "--solution", "--project",
"--include-symbol-kind", "--exclude-symbol-kind", "--optimize", "--help",
"--read-only", "--immutable",
];

internal const string IndexParallelismEnvironmentVariable = "CDIDX_INDEX_PARALLELISM";
Expand All @@ -32,6 +33,7 @@ public static IndexCommandOptions ParseArgs(string[] args)
bool quiet = false;
bool dryRun = false;
bool force = false;
bool readOnly = false;
bool yes = false;
bool watch = false;
bool optimizeOnly = false;
Expand Down Expand Up @@ -98,6 +100,11 @@ public static IndexCommandOptions ParseArgs(string[] args)
case "--force":
force = true;
break;
case "--read-only":
case "--immutable":
readOnly = true;
parseError ??= $"{args[i]} is only supported by query commands; index mutates the database and cannot run read-only";
break;
case "--yes":
yes = true;
break;
Expand Down Expand Up @@ -274,6 +281,7 @@ public static IndexCommandOptions ParseArgs(string[] args)
EasterEgg = easterEgg,
DryRun = dryRun,
Force = force,
ReadOnly = readOnly,
Yes = yes,
Watch = watch,
OptimizeOnly = optimizeOnly,
Expand Down
11 changes: 11 additions & 0 deletions src/CodeIndex/Cli/IndexCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ internal static int Run(string[] indexArgs, JsonSerializerOptions jsonOptions, C
using (indexLock)
{
using var db = new DbContext(dbPath);
if (db.ReadOnlyFallback)
{
return WriteCommandError(
options.Json,
jsonOptions,
$"database opened through stale read-only fallback after WAL checkpoint failed: {resolvedDbPath}; index requires a writable database",
CommandExitCodes.DatabaseError,
"Move the database to writable storage, stop the writer holding the WAL lock, or rerun the query command with --read-only if you only need read access.",
CommandErrorCodes.DbNotWritable);
}

// Capture prior readiness BEFORE we clear it. Update mode (--commits / --files) only
// touches a subset of files, so trust bits the DB did NOT previously carry must not
Expand Down Expand Up @@ -1154,6 +1164,7 @@ public sealed class IndexCommandOptions
public string? EasterEgg { get; init; }
public bool DryRun { get; init; }
public bool Force { get; init; }
public bool ReadOnly { get; init; }
public bool Yes { get; init; }
public bool Watch { get; init; }
public bool OptimizeOnly { get; init; }
Expand Down
13 changes: 12 additions & 1 deletion src/CodeIndex/Cli/QueryCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ private sealed record StatusReadinessField(
"--bytes",
"--profile",
"--check-updates",
"--read-only",
"--immutable",
];
private const string OutputFormatText = "text";
private const string OutputFormatJson = "json";
Expand Down Expand Up @@ -2614,6 +2616,7 @@ public static int RunStatus(string[] cmdArgs, JsonSerializerOptions jsonOptions,
status.DataDir = options.DataDir;
status.DataDirSource = options.DataDirSource;
status.DataDirMode = DataDirectorySecurity.GetUnixModeString(GetDataDirectoryPath(options.DbPath));
status.DbFileMode = DbContext.GetUnixFileModeString(options.DbPath);
status.MacProfile = MacProfileDetector.DetectCurrent();
if (options.CheckWorkspace)
{
Expand Down Expand Up @@ -4404,6 +4407,7 @@ public static QueryCommandOptions ParseArgs(
bool exactName = false;
bool exactSubstring = false;
bool dbPathExplicit = false;
bool readOnly = false;
bool checkWorkspace = false;
TimeSpan? staleAfter = null;
HashSet<string>? statusCheckScopes = null;
Expand Down Expand Up @@ -4521,6 +4525,10 @@ void WarnIfDuplicateSingleValueOption(string canonicalName, string newValue)
else
AddParseError(dbPathError!);
break;
case "--read-only":
case "--immutable":
readOnly = true;
break;
case "--workspace-db":
if (TryReadStringOptionValue(args, ref i, "--workspace-db", inlineValue, allowSeparatedDashPrefixedLiteralValue: true, out var workspaceDbPath, out var workspaceDbError))
workspaceDbPaths.Add(workspaceDbPath!);
Expand Down Expand Up @@ -5041,11 +5049,13 @@ void WarnIfDuplicateSingleValueOption(string canonicalName, string newValue)
AddParseError(defaultMaxLineWidthError);

var dbResolution = DbPathResolver.ResolveForQuery(Environment.CurrentDirectory, dbPath, dataDir);
var resolvedDbPath = readOnly ? DbContext.ToReadOnlyUri(dbResolution.DbPath) : dbResolution.DbPath;

return new QueryCommandOptions
{
DbPath = dbResolution.DbPath,
DbPath = resolvedDbPath,
DbPathExplicit = dbPathExplicit,
ReadOnly = readOnly,
DataDir = dbResolution.DataDir,
DataDirSource = dbResolution.DataDirSource,
Json = json ?? jsonDefault,
Expand Down Expand Up @@ -7596,6 +7606,7 @@ public sealed class QueryCommandOptions
{
public string DbPath { get; init; } = Path.Combine(".cdidx", "codeindex.db");
public bool DbPathExplicit { get; init; }
public bool ReadOnly { get; init; }
public string? DataDir { get; init; }
public string? DataDirSource { get; init; }
public bool Json { get; init; }
Expand Down
Loading
Loading