Skip to content
CADCAMFUN
Docs

Documentation

Review and restore a project change

Record the working database, isolate a proposal branch, review the diff, run committed checks, and merge or restore with clear side effects.

Separate saved resources from committed history

Saving a project, drawing, or toolpath updates the SQLite working copy. Creating a project commit snapshots the working resources into Git-compatible objects and moves the current branch ref. Those are separate operations: a saved drawing can be uncommitted, and a committed program can remain local without any remote mirror.

The sidecar's history service builds one canonical JSON file per resource. The tree includes project.json, drawings, components, toolpaths, referenced materials/tools/machine configurations, and production costs. Workflow YAML and other carried non-entity text live in the repository tree, rather than ordinary drawing records. A change to one entity does not require treating the entire database as an opaque binary diff.

Choose a disposable or intended project before following the mutating examples. Set PROJECT_ID to its real identifier and use a current connection URL/application bearer. Project history and workflow endpoints are documented in the history API reference; project metadata writes use the projects API.

Objects use git's byte-exact loose-object format, so commit and tree IDs match what git computes for the same content. Branch references live under refs/heads/, and the default branch is main.

Inspect identity and working state

Read access, commits, branch creation, opening pull requests, reviews, and workflow dispatch require access to the project. Checkout, restore, remote configuration, push/pull, and merging require maintainer access: the project owner or organization ADMIN/MANAGER. An optional account session header selects the acting user; without a valid account session the application uses its local profile.

Read status before changing branches
curl --fail-with-body \
  -H "Authorization: Bearer $CADCAMFUN_TOKEN" \
  "$CADCAMFUN_URL/api/projects/$PROJECT_ID/vcs/status"

The response names the branch, nullable HEAD, and dirty paths. Dirty means that current resource files differ from HEAD. A non-empty list is useful review information, not a storage failure. Before moving the working copy, decide whether those changes belong in a commit or should be deliberately restored/discarded.

If HEAD is null, record the first project commit before creating a branch from HEAD. The branch API resolves a real source commit; there is no placeholder commit to branch from in a new project.

Create a baseline or record intended edits

A commit requires a non-empty message and captures the current database-backed resources. Review dirty paths first: the API does not stage individual entity files. The optional files field adds safe workflow definitions, described later; it does not select which drawing edits to include.

Record the current working copy
curl --fail-with-body \
  -H "Authorization: Bearer $CADCAMFUN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"message":"Record baseline blank"}' \
  "$CADCAMFUN_URL/api/projects/$PROJECT_ID/vcs/commit"

The response is a 201 commit record with object ID, parent IDs, message, and author information. Record its OID for a later comparison or restore. An unchanged tree is rejected with NothingToCommit rather than manufacturing another revision from the same content.

authorKind defaults to user. Assistant-originated commits use ai and cannot commit on main. Actor policy is enforced by the project service; a different display message does not bypass it. Creating a commit can trigger workflows after the commit is recorded. A subsequent failed workflow does not undo that commit.

Create and check out a proposal

Creating a branch writes a ref; it does not switch the working database. Checkout is explicit and refuses uncommitted working changes. The following calls create a branch from the current HEAD and load it as the checked-out branch.

Branch creation and checkout are separate
curl --fail-with-body \
  -H "Authorization: Bearer $CADCAMFUN_TOKEN" -H "Content-Type: application/json" \
  --data '{"name":"proposal/blank-label"}' \
  "$CADCAMFUN_URL/api/projects/$PROJECT_ID/vcs/branches"

curl --fail-with-body \
  -H "Authorization: Bearer $CADCAMFUN_TOKEN" -H "Content-Type: application/json" \
  --data '{"branch":"proposal/blank-label"}' \
  "$CADCAMFUN_URL/api/projects/$PROJECT_ID/vcs/checkout"

To start elsewhere, add a from revision to branch creation. Names must be safe refs: traversal, repeated slashes, a trailing slash/.lock suffix, and ref-expression fragments are rejected. Reusing an existing name is also refused.

After checkout, save the intended project changes through their resource APIs. For a small demonstration, update project metadata with PUT /api/projects/:id and a new name, then commit it. The resulting diff changes project.json; it does not imply a geometry change. In a real part revision, save the drawing and any regenerated toolpaths before committing.

Read the exact changes under review

GET /vcs/log supports rev, limit, and an optional tracked path. GET /vcs/diff requires to and accepts an optional from. Use explicit OIDs for a reproducible comparison. Encode branch names and JSON paths as query values rather than splicing unescaped punctuation into URLs.

Compare the proposal with main
curl --fail-with-body --get \
  -H "Authorization: Bearer $CADCAMFUN_TOKEN" \
  --data-urlencode "from=main" \
  --data-urlencode "to=proposal/blank-label" \
  "$CADCAMFUN_URL/api/projects/$PROJECT_ID/vcs/diff"

Entity changes include path, resource kind, added/modified/deleted status, optional ID/name, and property changes described by JSON pointers. Canonical JSON sorts object keys recursively while preserving array order. Non-overlapping object-property edits can merge structurally; incompatible concurrent array edits are atomic conflicts, not element-by-element reconciliations.

Review manufacturing evidence as part of the toolpath record: matching G-code and artifact identity matters more than a familiar filename. A project commit captures saved data; it does not run a missing native manufacturing request just because the tree contains NC text.

Commit executable checks with the project

The public commit endpoint's extra files are restricted to .cadcamfun/workflows/<safe-name>.yml or yaml. It accepts text values, not arbitrary filesystem patches. You cannot use this field to replace drawing JSON, write shell scripts, or delete a file by sending null.

A definition is committed as YAML text, then the runner loads it from that committed ref. The example below validates structural files, requires NC for existing toolpaths, checks operation sequencing, and attempts replay for toolpaths carrying a stored generator request.

.cadcamfun/workflows/review.yml
name: Review checks
on:
  manual: true
  pullRequest:
    branches: [main]
jobs:
  inspect:
    steps:
      - id: structure
        uses: project/validate
      - uses: project/require-gcode
      - uses: cam/sequence
      - id: replay
        uses: cam/verify

To commit it from a script, construct the JSON body with a serializer rather than hand-escaping YAML:

Body for POST /api/projects/:id/vcs/commit
const body = {
  message: "Add committed review checks",
  files: { ".cadcamfun/workflows/review.yml": await Bun.file("review.yml").text() }
}
const encoded = JSON.stringify(body)

The commit still snapshots all current working resources. Validate the YAML before recording it: the history commit can succeed while the resulting invalid workflow is later recorded as a failed run. The production engine checks schema, dependencies, cycles, duplicate step IDs, and registered uses identifiers.

Dispatch checks and interpret their evidence

POST /api/projects/:id/actions/dispatch accepts an optional ref and runs every manual workflow matching that committed ref. It is not a single-workflow-name selector. Read definitions at actions/workflows and persisted outcomes at actions/runs. The service returns the latest 50 runs by default.

A run records workflow, trigger, ref, SHA, timestamps, status, and events. Jobs follow dependency levels; independent jobs can run concurrently, while steps within a job are sequential. Unknown uses identifiers fail before execution. An if value is success, failure, or always, not an arbitrary expression. A failed dependency skips its downstream job entirely.

Interpret CAM outputs carefully. cam/verify and cam/gcode inspect committed Toolpath.data.request. Missing requests, unsupported toolpaths, or missing NC may be reported in skipped outputs. A successful run with verified=0 is not verification evidence. project/require-gcode checks existing toolpaths; a project with no toolpaths can satisfy it vacuously.

Automatic HTTP push-trigger integration currently supplies no changed paths, so a push paths filter does not match those events. Use branch-only filters or explicit manual dispatch for this integration. HTTP live started/finished events are published after the synchronous run returns; use saved runs for durable results.

The writing workflow step reads current state

The current vcs/commit step delegates to project commit without a clean-working-copy guard. It commits current working database resources on the checked-out branch, which can differ from the run's ref. Its allowed extra files do not prevent that broader snapshot side effect. Use it only when that current branch and working state are intended to be committed.

Open, review, and merge a proposal

Open a local pull request with title, head, optional body, and base (main by default). Head and base must differ, and a base already containing the head has nothing to merge. The response includes a project-local number used by review and merge endpoints.

Propose the committed branch
curl --fail-with-body \
  -H "Authorization: Bearer $CADCAMFUN_TOKEN" -H "Content-Type: application/json" \
  --data '{"title":"Review blank label","head":"proposal/blank-label","base":"main","body":"Metadata change and committed checks."}' \
  "$CADCAMFUN_URL/api/projects/$PROJECT_ID/pulls"

GET /pulls/:number returns the request, reviews, and changes. Reviews accept comment, approve, or request-changes, with optional path/pointer. The latest non-comment verdict per reviewer controls the request-changes block. A later comment does not clear it; that reviewer must submit an updated approve verdict. The implementation has no positive approval-count requirement when no blocking verdict remains.

POST /pulls/:number/merge requires maintainer access. AI actors cannot merge into main. If the base is checked out, the service requires a clean working copy and applies the merged snapshot to the database. If another branch is checked out, it updates the base ref without replacing that current working database. Merges may fast-forward or create a merge commit; inspect ancestry rather than assuming two parents.

Merged pull-request diffs use the recorded merge commit, preserving the review view after refs move. Local pull requests are application review records, not automatically GitHub pull requests.

Restore content without rewriting history

POST /vcs/restore takes a rev and optional paths. It requires maintainer access and a clean working copy, and creates a new commit containing the earlier content. It does not reset the branch backward and erase intervening commits. The service then applies the new HEAD to the working database.

Restore one saved drawing from an earlier commit
{
  "rev": "earlier-commit-oid",
  "paths": ["drawings/actual-drawing-id.json"]
}

Replace the path with a real tracked resource and inspect its diff first. Omitting paths restores the full earlier tree; that can replace or remove multiple related resources. Whole-project snapshots also carry referenced library items and costs so restored toolpaths can retain dependencies. Resource-version restore and project-history restore have different scopes.

After restoring manufacturing source, inspect whether saved native output still matches the intended drawing revision. A history operation preserves the earlier artifact; it does not transform that artifact into evidence for a newly modified drawing.

Mirror committed history and recover conflicts

A GitHub mirror is optional. Set remote to an owner/repo string with PUT /vcs/remote, or null to detach. Push and pull use the user's GitHub token in x-github-token for that call; it is not saved in project JSON. Maintainer access is required for these HTTP operations.

Push uploads reachable objects and refuses to overwrite unpulled remote commits. Pull requires a clean working copy, fetches the remote branch, and merges. A structural JSON conflict identifies paths and pointers without writing a partial repository merge commit. Inspect both sides, create a deliberate resolution, and retry instead of forcing one side over the other.

HTTP 409 usually means dirty state or merge conflict; 403 can mean a maintainer or AI-main restriction; 404 can mean inaccessible project or unknown revision; 400 covers invalid names, empty messages, or no changes. A database failure maps to a server storage error. Preserve the OIDs and failure message when diagnosing a mismatch.

Source provenance: packages/vcs/src/repository.ts, json.ts, snapshot.ts, github.ts; apps/sidecar/src/services/project-vcs.ts, repos/project-vcs-repo.ts, and http/vcs.ts; workflow schema/runner in packages/workflow/src and sidecar workflow-runner.ts/workflow-steps.ts.

How reviews decide a merge

  1. Open a pull request from a feature branch (an AI actor must use an ai/* branch) into main.
  2. Reviewers submit comment, approve, or request-changes, optionally anchored to a file path and a JSON pointer.
  3. At merge time, the service takes each reviewer's latest non-comment verdict. If any of them is request-changes, the merge fails with blocked: "A reviewer requested changes".
  4. If the base branch is checked out, its working copy must be clean.
  5. The merge is three-way per JSON property. Conflicts are returned with their paths and pointers, and nothing is committed.
  6. Workflows with on.pullRequest matching the base branch run when the pull request is opened, so their results are available during review.
SituationWhat to do
blocked for an AI actor on mainCommit on an ai/* branch and open a pull request; a person merges it.
conflict on /width of drawings/d1.jsonDecide the value on one branch, commit, and merge again.
dirtyCommit or discard local edits first; automated steps never absorb unfinished work.
nothing-to-commitThe saved resources already match the last commit.

Restore recipes

Restore without rewriting history
// Bring back one drawing as it was three commits ago
const old = repo.log()[3].oid
repo.restorePaths(old, ["drawings/d1.json"], me, "Restore plate outline")

// Make the whole tree equal to an older commit (a new commit on top)
repo.revertTo(old, me)               // message defaults to "Restore <rev>"

// Who changed a toolpath, most recent first
repo.fileLog("toolpaths/op1.json").map((e) => e.message)

Both restore operations add a new commit, so the older state can itself be undone and the history stays shareable. Restoring a toolpath file without the material or tool it references can leave dangling references; restore those library files in the same call.

A complete review session, captured

Every response below was captured from a running sidecar on a fresh project (ids shortened). Paths are relative to /api/projects/{id}/.

branch → change → diff → PR → review → merge
# fresh project with one drawing
GET  vcs/status
{"branch":"main","head":null,"dirty":["drawings/2f315a02-….json","project.json"]}

POST vcs/commit       {"message":"Baseline"}
{"oid":"8bdded63…","parents":[],"message":"Baseline\n","author":{"name":"Local user",…}}

POST vcs/branches     {"name":"thicker"}
{"current":"main","branches":{"main":"8bdded63…","thicker":"8bdded63…"}}

POST vcs/checkout     {"branch":"thicker"}
{"branch":"thicker","head":"8bdded63…","dirty":[]}

PUT  /api/drawings/{id}  {"name":"Plate 12mm"}
GET  vcs/status
{"branch":"thicker","head":"8bdded63…","dirty":["drawings/2f315a02-….json"]}

POST vcs/commit       {"message":"Rename plate"}
{"oid":"a6869011…","parents":["8bdded63…"],…}

GET  vcs/diff?from=main&to=thicker
[{"path":"drawings/2f315a02-….json","kind":"drawings","id":"2f315a02-…","name":"Plate 12mm",
  "status":"modified","changes":[
    {"pointer":"/name","before":"Plate","after":"Plate 12mm"},
    {"pointer":"/updatedAt","before":"…09:20.069Z","after":"…09:20.168Z"}]}]

POST pulls            {"title":"Thicker plate","head":"thicker","base":"main"}
{"number":1,"state":"open","authorKind":"user","mergeCommit":null,…}

POST pulls/1/reviews  {"verdict":"approve","body":"Looks good"}
{"verdict":"approve","body":"Looks good","path":null,"pointer":null,…}

POST pulls/1/merge
{"number":1,"state":"merged","mergeCommit":"a6869011…",…}

POST pulls/1/merge    (again)
{"_tag":"BadRequest","message":"Pull request is not open"}
What you seeWhat it means
head: null before the first commitThe repository exists but has no commits yet; every resource is dirty
Dirty paths such as drawings/<id>.json, project.jsonThe working copy is serialised as one JSON file per resource
A new branch points at the current headWithout from, the branch starts at the current head
Diff changes are JSON pointersChanges are reported per property (/name), not per line
updatedAt also changedTimestamps are versioned with the resource, so every save shows up
mergeCommit equals the branch headmain had not moved, so the merge fast-forwarded
Merging twice → 400Only open pull requests can be merged or closed

Review and query fields

RequestFields
POST pulls/{number}/reviewsverdict (required): comment, approve or request-changes; optional body, and path + pointer to anchor the review on one property. Any other verdict is rejected with 400.
GET pulls/{number}Returns pullRequest, its reviews and the structural changes between base and head
GET pulls?state=…Filter the list by state
GET vcs/log?rev=&limit=&path=History from a revision, limited, optionally for one resource path
GET vcs/diff?from=&to=Structural diff between two revisions or branches
POST vcs/restorerev (required), optional paths. An unknown revision answers 404 Unknown revision <rev>.
GET/PUT vcs/remote, POST vcs/push, POST vcs/pullConfigure the mirror remote and push or pull a branch

In 3D

Commits, parents and merges Drag to orbit · scroll to zoom · right-drag to pan
The last 14 commits of origin/main from git log --format=%h|%p: each pull request branch commit (back lane) is merged into main (front lane, orange merge commits), then the docs commits. A project history in the app is the same kind of graph: commits with parents, refs pointing at them.