Repository navigation
Description
The need
A project should be able to answer "what was done here, when, and with what?". That serves reproducing an analysis, reviewing a student's or a colleague's work, or writing a paper's methods section. Today RustGIS keeps three partial records:
- The History pane (
Project::gp_history) keeps geoprocessing runs only: up to 500, saved with the project. They are undone with the project, so an undone Buffer leaves no trace. - Record Task keeps the steps the user chose to record, as a task, and skips anything holding a secret.
- The MCP audit log (
docs/mcp.md) keeps an agent's tool calls in the app-data folder, one file a day, not with the project.
None of them records the rest: symbology, labels, layers added or removed, edits, layouts, exports, cube and raster commands, or a script's or the assistant's commands. And none shows what was undone.
Proposal
A project log: one entry for each command run on the project, from the app, a script, the assistant, the CLI, Python or MCP, kept in the .rgis file.
Each entry:
| Field | What it holds |
|---|---|
at |
when it started: UTC, in milliseconds since 1970 |
command |
its id (layer.symbology, gp.run, cube.trend) |
params |
its parameters, with secrets removed and large values clipped (below) |
ok |
whether it succeeded |
error |
the error text when it failed, clipped |
millis |
how long it took |
New commands:
project.log {"since"?, "until"?, "command"?, "failed"?, "limit"?}: the entries, newest last.commandis an id or a prefix (layer.).project.exportLog {"path", "format"?: "csv" | "jsonl"}: writes the log out. CSV has one row per entry, with the parameters as a JSON column.project.clearLog: empties it. It is undoable like any other change, and it leaves its own entry, so a cleared log says it was cleared.
In the app, the History pane gets a second view, Project Log, beside the geoprocessing runs. It has a filter by command and a Save As button for the CSV or JSONL. That UI is part 2, after the engine part below.
Choices in this proposal
-
Recorded in
Session::run, at depth 0. Every front end reaches the project through it, so one place covers them all. A command run by another command (a script's lines, a model's steps) is inside its parent's entry:script.runis logged with its script, andgp.runwith its tool and parameters. -
Commands that change the project, or that reach a file or a service. Two kinds are logged:
- every command with
spec.mutates(all but those built with.view()); - a
.view()command whose parameters name a file, folder, address or connection, such as an export orproject.save. These are the keys the plugin permission gate already checks (PLACE_KEYSincrates/engine/src/cmd/plugins.rs).
Everything else is a
.view()command that reads only: listing layers or commands, describing a tool, reading features, and view moves (map.goToXY,map.zoomIn). An agent or a script calls those hundreds of times, and they change nothing, so they are left out.ui.*commands never reach the engine. Undo and redo are logged (edit.undowith the label of the step it took back), so the log shows what was undone. - every command with
-
Outside the undo snapshots. The log is kept beside the project's undo history, not in it, and written into the
.rgison save. Undoing a command doesn't erase its entry, and an undo snapshot doesn't copy the log. -
Secrets never written. The parameters go through the redaction History's Copy as code already applies (
redactedincrates/engine/src/cmd/history.rs: addresses lose their keys and tokens, and a parameter named as a password, key, secret or token is emptied). An error that mentions a credential is withheld, as the audit log does. -
Bounded. Each entry's parameters are clipped to 4 KB of JSON: a long string is cut, and an array of more than 100 items keeps its first 100 with a count. This matters for pasted GeoJSON and long scripts. The log keeps its last 10,000 entries, the oldest dropped first: at most 40 MB (10,000 × 4 KB), usually far less. Both limits are constants and stated in the docs.
-
On by default, and saved with the project. That matches History, which already saves its runs' parameters (with local paths) in the
.rgis. A project field,logCommands(defaulttrue, set withproject.logCommands {"on": bool}), turns it off, andproject.clearLogempties it before a project is shared. The docs say that a shared.rgiscarries its log. -
Older projects: a project with no log opens with an empty one, and the field is skipped when it is empty, so a project that never logged anything saves as it does today.
-
Not a replacement for History. History keeps its outputs, its Copy as Python / Command / CLI and its Save as Python Script. The log is a plain record and runs nothing again.
Out of scope
- Logging what isn't a command: pans and zooms with the mouse, a pane opened. Those are UI state, not changes to the project.
- A log shared across projects or a user-wide log: the MCP audit log covers agents.
- Replaying the log: a task (New Task From History) or History's script already do that for what can be run again.
Plan
- Engine:
LogEntry, recording inSession::run,project.log,project.exportLog,project.clearLog, saving and loading in.rgis, andproject.logCommands. Tests cover depth 0 only, undo kept, redaction, clipping, the cap, an old project opening, and CLI and Python runs logged. Docs go indocs/user-guide/data.md(projects) anddocs/cli.md. - App: the History pane's Project Log view, with its filter and Save As.
I can do both, as two PRs. Since this adds a field to the .rgis format, I'd like your go on the design first.
Activity
Projects: the log's review fixes (opengeos#797)
Projects: a file's log is read keeping its last entries, each bounded…
Projects: nothing in a file's log keeps a project from opening; its e…
Projects: secrets in a text's JSON are taken out; turning the log off…
Projects: a JSON object the clip cuts short keeps nothing after its b…
Projects: making a project, or opening one from text, starts its log;…
Projects: an embedded object that doesn't parse keeps nothing after i…
Projects: the log's last-resort text is cut by bytes (opengeos#797)
Projects: clearing the log while it is off is recorded; a plugin comm…
Projects: reading the log takes the file permission; saving it doesn'…
Projects: only a plugin's pending answer waits to be logged; a log re…
Mavrick-F commentedon Oct 28, 2026
@giswqs @elpidioassinaturas
When this issue was opened, it asked for a go on the design before the work started, since it adds a field to the
.rgis. That go never came, and part 1 (#947) is now well along. Before more review rounds go into it, I think the design questions need an answer. The code isn't the problem: CI is green and the review has been thorough.Two open questions:
.rgis, by default and up to 10,000 entries. The.rgisis the file people share, so a shared project carries file paths, SQL text and service addresses unless someone knows to clear the log first. History already saves its runs' parameters there, so there's a precedent. This is a much larger record, though.A way to merge most of the work now: split #947 so the first PR keeps the log for the session only, in memory, without writing it to the
.rgis. Everything that doesn't depend on storage would land:Session::runhook and the rules for what's logged;project.log,project.exportLogandproject.clearLog;Storing the log in memory removes the shared
Arc<Mutex>, the relative paths and the cached save text, and theFORMATquestion no longer comes up. Saving the log, in the.rgis, in a separate file or as part of a merged History, can follow once the questions above are settled, and nothing in the first PR would need undoing.giswqs, which way would you like this to go?
elpidioassinaturas commentedon Oct 30, 2026
@Mavrick-F, agreed, the split is the better path. Part 1 is ready as a session-only log, held in memory on
Sessionand never written to the.rgis, its recovery copy, a package or a template. It keeps:Session::runhook and its rules for what's logged;project.log,project.exportLog,project.clearLogandproject.logCommands;That drops the shared
Arc<Mutex>, the relative paths, the cached save text and theFORMATquestion, andproject.rsis back as onmain. Clearing or switching the log no longer changes the project either. The docs now describe the log as an audit trail of what was done in the session, not as a way to reproduce an analysis. Local CI passes.How the log gets saved, and how it relates to History and recorded tasks, stays open here. @giswqs, if the split works for you, I'll push it to #947 (it replaces the branch's log-in-the-project version) and update the PR text.
feat(project): keep the log for the session only, in memory (opengeos…
docs(project): a document is kept as its size past 200 characters (op…
docs(project): a long path with no dot reads as base64; a cut list's …
deniial00 commentedon Oct 31, 2026
I agree with @Mavrick-F's concerns and support the proposed session-only approach in #947.
Mavrick-F commentedon Oct 31, 2026
@opengeos/rustgis-maintainers @elpidioassinaturas
#947 (the session log) and #1181 (Diagnostics) are both close, and #1216 asks whether they should share a pane. So here's a picture of how the records fit together, which may also answer the two questions still open here.
RustGIS will soon have several records of what happened. They differ mainly in their rules, not in how they look:
.rgisTasks are different again: steps the user chose to keep and run again.
Suggested answers to the two open questions:
.rgis. History already answers "how was this made" there, and with undo rules the log can't share. If people want the log kept across sessions, an opt-in file in app-data, like the MCP audit log, keeps the project file to the project's current state.The MCP audit log stays its own record too. It keeps reads as well as changes, for 30 days, and the user can't turn it off from inside a session, so it can't be a filtered view of the session log.
One possible follow-up, not for #947: the log doesn't say who ran a command (the app, a script, the assistant, MCP, a plugin). Recording that would let it answer "what did the assistant change?".
Sessionhas no notion of a caller today, though, so each front end would have to declare itself. I think that's a good way to increase trust through transparency.Session log: every command run in a session, with its time, parameter…
History pane: Tool runs and Session log views; the log filtered, save…
elpidioassinaturas commentedon Nov 4, 2026
Done in two parts, both merged: #947 (the session log: every command run on a project, with its time, parameters and result) and #1324 (the History pane's Tool runs and Session log views, the log filtered and saved as CSV). Closing.
🤖 Generated with Claude Code