Notarizing documentation

User guide

Developer preview. Not yet production ready. This page is rendered from docs/user-guide.md of the Notarizing repository at revision d23579e737f9d1e16e07b3c6b21a51282d64027e. It describes the behavior of that revision.

Contents

This guide shows how to review an OpenSpec change with notarizing. You need Go to build the binary and Git to read your repository. To build the browser interface from source, you also need Node 22.12 or later at build time. You need no broker, no model and no Node at run time.

Notarizing reads your repository. It never edits it, never runs your checks and never fetches anything. Imported reports, repository text and review text are data. Notarizing never runs them as instructions.

1. Set up a workspace

A workspace is one directory that holds one SQLite database. One process owns it at a time.

  1. Build and install the binary with the browser assets:

    make install

    make install runs make web and then go install. make web installs the locked npm packages and builds the browser interface into internal/web/dist. go install embeds it into the binary and writes the binary to the directory of go env GOBIN. If GOBIN is empty, the directory is the bin directory of go env GOPATH.

  2. Add the install directory to PATH, and check that the shell finds the binary:

    export PATH="$(go env GOPATH)/bin:$PATH"
    notarizing version

    If you set GOBIN, add that directory instead. The rest of this guide runs notarizing from PATH. To use the build of make web and make build in its place, run bin/notarizing, or add the bin directory of the checkout to PATH.

    Without the browser assets, serve shows a notice page in place of the review interface, and doctor gives the warning browser assets. The CLI and MCP work without the browser assets.

    If you cannot read the private ka2a module, install the build without the ka2a adapter: make install TAGS=noka2a. That build needs no ka2a module. Its ka2a reload and serve --ka2a-config fail with exit code 5.

  3. Create the workspace:

    notarizing --workspace ~/reviews/ws init

    The command makes the directory with mode 0700 and the database with mode 0600. If the directory holds other files, the command refuses it.

  4. Set the workspace for the shell (optional):

    export NOTARIZING_WORKSPACE=~/reviews/ws

    Without --workspace and without this variable, notarizing uses ./.notarizing.

  5. Check the setup:

    notarizing doctor

    The exit code is 1 when a check has the status error.

2. Import a change

  1. Register the local Git repository under an ID of your choice:

    notarizing repo add payments ~/src/payments

    The ID has 1 to 64 characters: lower-case letters, digits, ., _ and -. The first character is a letter or a digit. If the checkout moves or you clone it again, point the ID to the new path:

    notarizing repo set-path payments ~/work/payments

    The command checks that the new path is a Git repository with the same object format that contains the latest imported commit. Otherwise it changes nothing and fails with conflict. There is no repo remove, because stored history names the repository ID. If the workspace may no longer show a repository, the operator revokes its source access with notarizing repo access revoke. See the operator guide.

  2. List the changes at a commit:

    notarizing change list --repo payments --at main
  3. Import one change at an exact revision:

    notarizing change import --repo payments --at main --change add-refund-limits

    --at takes a branch, a tag or a full commit ID. HEAD is not accepted. The import resolves the locator to one commit and stores that commit. If the branch moves later, the stored target does not change. Import the change again to review the new commit. If the locator does not resolve, nothing is stored.

  4. Read the change report:

    notarizing change report --repo payments --change add-refund-limits

    The report is useful before any check report exists. Each requirement without a binding or a report shows as unassessed.

A requirement without a Requirement-ID line has a provisional identity. A provisional requirement never matches a requirement of another revision. Review shows the badge Provisional in place of the ID, the inspector shows its provisional reference, and the Diagnostics tab lists it. The same block at two revisions is two objects.

3. Review in the browser

  1. Start the owner process:

    notarizing serve

    serve listens on 127.0.0.1 only. It prints the URL and a one-time viewer secret to standard error. Use --open to open the system browser. Use --port 0 to select a free port.

  2. Open the URL, paste the secret and select Open workspace. The secret works once. The page shows your browser session ID (ses_...) under "This session". To open another tab or browser, use the next secret in the file viewer.token of the workspace.

While serve runs, other notarizing commands send their imports through its control API. Read commands read the workspace read-only.

Address, localhost and SSH

The server answers only at the exact address that serve prints, for example http://127.0.0.1:7373/. It refuses every other host name with 403, also localhost and 127.0.0.1 with another port. This check stops DNS rebinding attacks, so it cannot be turned off. A page request with a wrong address shows a short page that names the correct address. An API request gets the error forbidden with the reason host.

If serve runs on another computer, forward the same port number over SSH:

ssh -L 7373:127.0.0.1:7373 build-host

Then open http://127.0.0.1:7373/ on your computer. The local port must be the same as the server port. A different local port, for example -L 8080:127.0.0.1:7373, gives the refusal page, because the browser then sends the Host 127.0.0.1:8080.

Review mode

Review lists each effective requirement of the selected change and snapshot. Each row shows:

  • the exact source of the requirement;
  • the assessment rows: the reported claim beside the evaluated status;
  • the missing dimensions, for example a required configuration without a report;
  • the stale reasons, for example a report about an older implementation revision;
  • the contradictions: applicable failing and passing reports of one row;
  • the assumptions, premise groups and open questions.

Select a row and press Enter to open the inspector. The inspector shows the requirement block, its evidence history, its review history and its relationships. Press Escape to go back.

A reported pass is not an established pass. The evaluated status is pass only when the policy trusts the source for the method and an approved evaluator made the report.

Each row of the evidence history names the source that submitted the receipt: the channel, the source namespace and, for a signed ka2a transport, the verified principal. A verified principal names the producer. It is not evaluator evidence: a signed proof claim stays unknown until a report of an approved evaluator exists.

A review decision applies only to the target where it was made. When the wording of a premise changes, the new target starts the premise open. The inspector shows the earlier decision with the warning that the source changed after it, and the Diagnostics tab lists the premise. Record a new decision at the new target.

The selected change, target, item and review checkpoint are in the address of the page. After an import with the CLI, reload the page: it opens the same selection with the new evidence.

Explore mode

Explore shows a bounded dependency graph around a root. Select the view:

  • dependencies: the premises and requirements that the root depends on;
  • impact: the claims that depend on the root;
  • gaps: requirements without approved coverage and assumptions without review;
  • selection: every relationship near the root.

Use the depth control and the relation filter to make the graph smaller. Machine suggestions are hidden by default. Select "Show suggestions" to see them. A suggestion is never followed as a dependency and never gives coverage. Select "Table" for a table of the same nodes and relationships.

The product makes one kind of suggestion: similar wording (notarizing.suggest/1). It compares the wording of the requirements of the target in the search index. The lexical method always runs. The embedding method runs only when the operator enabled the local provider and built the semantic index; it uses the stored vectors and never calls the model. Without a current search index there are no suggestions. In the inspector of a requirement, select "Show suggestions" under "Suggested similar wording" to see its suggestions with their method and score. A score is a similarity, not a confidence. To keep a suggestion, a reviewer selects "Propose a link". This sends the normal relationship.propose command with a rationale. The suggestion itself never becomes a link, a binding or coverage. In the CLI, run notarizing graph suggestions --target tgt_ID. A view that does not fit its limits says that it is partial and gives a next page. A graph is a review aid. Its reachability never establishes correctness.

Compare mode

Compare shows two exact revisions of a change side by side. It keeps these aspects apart: wording, assumptions, premise groups, relationships, review decisions and evidence applicability. Rows match only by stable ID. A model result can stay valid while the implementation correspondence is stale. Compare shows these two facts in separate columns.

To compare from the CLI:

notarizing compare --left tgt_LEFT --right tgt_RIGHT

To see which claims a draft change of assumptions could affect, use a what-if draft. The draft stores nothing:

notarizing graph whatif --target tgt_ID --remove ASM-FSYNC
notarizing graph whatif --target tgt_ID --replace "ASM-FSYNC=Fsync survives power loss on ext4"

The result lists the claims that are potentially affected under recorded dependencies and the premise groups that change. It does not invalidate historic evidence.

4. Give a review grant

A browser session can read. It can write review records only with an expiring grant from the CLI. Run the grant in a second terminal while serve runs:

notarizing review session grant --session ses_ID --ttl 30m --scope change:payments/add-refund-limits

The scope is workspace, repo:<repository_id> or change:<repository_id>/<change>. The time to live is 1 minute to 8 hours. A reviewer can add notes, decide on assumptions, propose and review relationships, and write wording drafts. A grant never allows imports, policy or binding changes, purges, provider changes or source edits.

To name the reviewer in review events and exports, add --actor "Alice Example". The events then show Alice Example (unverified, browser session XXXX). Notarizing does not verify the name, and the name changes nothing that the grant allows.

To end a grant:

notarizing review session revoke --session ses_ID

A restart of serve ends every session and grant.

If two tabs change the same object, one save commits and the other gets a conflict. The conflict shows the current revision and keeps your text. Read the new version before you submit again. A draft is "saved" only after the server confirms it.

5. Import evidence

Evidence is a notarizing.check-report/1 report with its artifacts. See the evidence producer guide for the format.

notarizing evidence import --artifacts ./artifacts report.json

Every declared artifact must be below the --artifacts directory with its declared length and SHA-256. A bad artifact rejects the whole report. The same report again returns the same receipt. The same report_id with other bytes is a conflict.

To import several reports, name several files, or name the output directory of the example producer gotestreport with --dir:

notarizing evidence import --dir ./reports

Each report is imported alone with its own receipt. If one report fails, the others are still imported. The command then lists each file with accepted, failed or not_attempted and exits with a non-zero code. The exit code is 4 when an outcome is unknown. Run the same command again: accepted reports return the same receipts.

A local file import has the attribution manual_unverified. Its reported pass is "not independently established" until the policy trusts the local source.

To read a receipt:

notarizing evidence show RECEIPT_ID
notarizing requirement history --repo payments RFD-R1-001

A producer cannot change a report. To retract or correct a result, the producer submits a new report. Then record the relation:

notarizing evidence correct --corrected OLD_RECEIPT --correcting NEW_RECEIPT --relation corrects --reason "Wrong fixture."

A correction keeps both receipts. It does not retire a failure.

Only a reviewed supersession retires a corrected failure. A reviewer with a review grant opens the requirement in the browser inspector and selects Retire failure by supersession. The button shows only for a failing receipt that a correction links to a later receipt. Enter the reason. The browser records two review events: a supersedes proposal from the later receipt to the failure, with the requirement and the dimension, and its acceptance. The server accepts them only when the correction exists and the later receipt is eligible and applicable under the policy. Then:

  • the assessment of that target uses the later receipt and marks the failure "Superseded by review";
  • both receipts stay, and the evidence history names the reviewer and the reason;
  • a view at an earlier review checkpoint shows the earlier result;
  • a later reject or investigate decision on the relationship revokes the effect.

evidence show, requirement history and change report show the supersession. The CLI cannot record one: it is a review decision.

6. Set the policy and the bindings

A binding links a requirement to an approved check. Only binding import creates bindings. A declared Evaluated-By link, a review decision or a report never does.

notarizing binding import bindings.json
notarizing binding show --repo payments

A bindings file (notarizing.bindings/1) names the repository, the checks with their method, target kind and approved evaluators, and the bindings with their required configurations. See examples/refund-limits/evidence/bindings.json. The JSON Schema bindings.schema.json describes the format for editors and other tools; binding import checks more, for example a check ID defined twice.

The policy (notarizing.policy/1, JSON Schema policy.schema.json) names the trusted source namespaces with their methods and the reviewed not_applicable exclusions:

{
  "schema_version": "notarizing.policy/1",
  "trusted_sources": [
    {"source_namespace": "local", "methods": ["implementation_test", "integration_test"]}
  ],
  "exclusions": []
}
notarizing policy set policy.json
notarizing policy show

A policy set with a changed policy stores a new version. The same policy again keeps the current version. The browser can never change the policy.

7. Read the change report

The change report gives no overall pass badge. Read each requirement row by row.

Evaluated statusMeaning
passAn eligible, applicable and complete report supports this dimension under the selected policy.
failAn eligible, applicable and complete report establishes a scoped violation under the selected policy.
staleThe report is about another revision of the requirement, the implementation, the model inputs, the correspondence inputs or the checker.
unknownNo conclusion: for example an untrusted source, an incomplete run, an integrity failure or a report without a conclusion.
not_applicableA reviewed policy exclusion makes the check not applicable.

Each row also has a coverage value: complete, partial or not_assessed. The reasons of a row say why it has its status. A later pass never clears an earlier failure. Both stay visible as an unresolved contradiction until a reviewed supersession resolves it.

Use --format markdown or --format json for a document. Use --checkpoint N to pin a review checkpoint.

8. Export

Each export pins one target and one review checkpoint. The same request gives the same bytes.

  • A graph as Mermaid, Markdown or notarizing.graph/1 JSON:

    notarizing graph export --target tgt_ID --object RFD-R1-001 --view dependencies --format mermaid --output graph.mmd
  • The review of a change as Markdown or JSON:

    notarizing review export --repo payments --change add-refund-limits --checkpoint latest --output review.md
  • A wording draft as a proposal-only patch:

    notarizing review patch --draft EVENT_ID --base FULL_SHA --output change.patch

    The patch replaces the complete requirement block and keeps every other byte of the file. If the base file differs from the file of the draft, the command reports a conflict. Notarizing never applies the patch. Review it against your working tree, then apply it yourself.

In the browser, the Export dialog writes the same formats for the current view. The inspector shows the before and after text of a wording draft.

notarizing search --query RFD-R1-001
notarizing search --query "refund limit" --kind requirement

An exact Requirement-ID or declaration ID comes first. Then come FTS5 full-text results. A missing or stale index shows as coverage pending, partial or stale, never as "no match". To rebuild an index, run notarizing search index --target tgt_ID.

An index is partial when it omits part of the selected source, for example a change document that is not valid UTF-8. A search with no result in a partial index says that the index is incomplete. It does not say that nothing matches.

In the browser search dialog, the scope "Only the items of this change" reads like a change-scoped graph: the accepted baseline and the review notes on it are hidden. Search then shows no result, excerpt or count of a hidden item.

Without a configured embedding provider, the browser search dialog disables the Hybrid option and says why. Lexical search works without a network call.

The query is sensitive. Shell history can keep --query. For sensitive text, use --query-stdin.

Hybrid search (--mode hybrid) needs the local Ollama provider. It is off by default. See the operator guide.

All Notarizing documents