diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15f9581..98256a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,11 @@ # Contributing to Oot -Oot is the court for code. It adjudicates **changes**, not commits or branches. A change is a content-addressed delta between two snapshots, and it can arrive from a human on git, an agent on Jujutsu, or a model in memory. If you have been burned by repository-level permissions, by a secret that should never have been a file, or by a diff that went public too early, this is your project. +Oot is the court for code. It adjudicates **changes**, not commits or branches. A change is a content-addressed delta between two snapshots, and it can arrive from a human on git, an agent on Jujutsu, or a model in memory. The bridge runs both ways: git and Jujutsu are first-class sources, and the exporter hands history back as byte-identical git. If you have been burned by repository-level permissions, by a secret that should never have been a file, or by a diff that went public too early, this is your project. ## Who should contribute -- **VCS adapter authors.** Oot reads snapshots from git and Jujutsu but owns neither. Turning those snapshots into Changes is real, unglamorous work. +- **VCS adapter and exporter authors.** Oot reads snapshots from git and Jujutsu, keeps its own store, and exports back to git. Turning snapshots into Changes, and stores back into git history, is real, unglamorous work. +- **Store & exporter contributors.** `.oot/` holds a bare git object database plus a change-id DAG; export rewrites trees under visibility policy while staying byte-faithful when unfiltered. Storage people welcome. - **Policy people.** Embargoed releases are run by hand today (GitHub Security Advisories, the Git project's git-security list). The people who have done this know where it leaks. - **Language experts.** The structural engine needs heuristics for what counts as a real conflict in each language. - **Anyone who has shipped the wrong merge.** Your war stories become our test cases. @@ -18,13 +19,14 @@ A **Change** is a delta between two snapshots. It carries an **Intent** (what it The repo is a seed, not the finished runtime. The build order is fixed because each piece feeds the next. 1. **Change ingestion.** Adapters that turn git and Jujutsu snapshots into the Change type. This is the front door. -2. **Visibility policy.** The governance spine. Private paths, private branches, embargo schedules. Driven by a config file, not code. This leads because the `.env`, monorepo-privacy, and private-branch problems are the reason Oot exists. -3. **Meaning disputes.** The structural engine (tree-sitter today) plus a hosted intent check. Flags changes that agree on tokens but disagree on meaning. One axis, after visibility. -4. **Docket format.** The on-disk record of an adjudication, including visibility and embargo state, so a human can review later. -5. **In-memory execution.** The path that runs with no materialized tree, for agents. -6. **Hosted model client.** The intent scoring and embargo distribution. The only part that is not open source. +2. **Store & exporter. DONE.** The `.oot/` store keeps history natively: bare git odb, change-id DAG, `record`, `log`, `status`, and export that reproduces byte-identical SHAs when unfiltered and strips private paths when filtered. +3. **Visibility policy.** The governance spine. Private paths, private branches, embargo schedules. Driven by a config file, not code. This leads because the `.env`, monorepo-privacy, and private-branch problems are the reason Oot exists. +4. **Meaning disputes.** The structural engine (tree-sitter today) plus a hosted intent check. Flags changes that agree on tokens but disagree on meaning. One axis, after visibility. +5. **Docket format.** The on-disk record of an adjudication, including visibility and embargo state, so a human can review later. +6. **In-memory execution.** The path that runs with no materialized tree, for agents. +7. **Hosted model client.** The intent scoring and embargo distribution. The only part that is not open source. -The original one-line pitch was "Git settles lines, Oot settles meaning." That framing is now one axis of three. Governance (visibility and embargo) leads; meaning follows. +The original one-line pitch was "Git settles lines, Oot settles meaning." Governance (visibility and embargo) leads; meaning follows; custody carries both. ## The docket contract @@ -50,17 +52,30 @@ A dispute has four required fields: `id`, `location`, `kind` (`meaning` or `visi ## Dev setup -You need a recent Rust toolchain. Once the runtime exists: +You need a recent Rust toolchain. ```bash cargo build --release cargo test ``` -Run the runtime against a fixture change to see a docket: +Run the runtime against this repo's fixtures to see a docket: ```bash -./target/release/oot adjudicate --change feature/auth-refactor +./target/release/oot adjudicate --change feature/auth-refactor \ + --base fixtures/repo/base --head fixtures/repo/head \ + --visibility fixtures/visibility.toml +# exits 1 on purpose: the fixture touches secrets/.env, so it cloaks +``` + +Walk the native store path, end to end: + +```bash +./target/release/oot init +./target/release/oot status +GIT_AUTHOR_NAME=you GIT_AUTHOR_EMAIL=you@example.com ./target/release/oot record -m "my change" +./target/release/oot log +./target/release/oot export --out /tmp/exported # add ./visibility.toml to filter private paths ``` ## Conventions @@ -73,7 +88,7 @@ Run the runtime against a fixture change to see a docket: ## License -The adjudication runtime, docket format, and adapters are MIT licensed. The hosted model is a paid service and is not part of this repo. Contributions to the open parts land under MIT. +The adjudication runtime, the docket format, the adapters, and the store with its exporter are MIT licensed. The hosted model is a paid service and is not part of this repo. Contributions to the open parts land under MIT. ## Start here diff --git a/Cargo.toml b/Cargo.toml index 1ae9ff7..a6f35f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "oot" version = "0.1.0" edition = "2021" -description = "Git settles lines. Oot settles meaning." +description = "Repos track lines. Oot governs changes: who may see them, what they mean, and when they may ship." license = "MIT" [dependencies] diff --git a/README.md b/README.md index c121552..5ead375 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Repos track lines. Oot governs changes: who may see them, what they mean, and when they may ship. -Oot is the court for code. It does not manage your commits or your branches. It adjudicates your **changes**: who is allowed to see a change, when it may become public, and what it means. A change can come from a human on git, an agent on Jujutsu, or a model running in memory. Oot judges all of them the same way. The project began as a five minute sketch about semantic merge conflicts. The real target is wider: governance over changes, with meaning as one axis among three. +Oot is the court for code. It governs your **changes**: who may see them, what they mean, and when they may ship. Governance needs custody, so since August 2026 Oot keeps its own history in a native `.oot/` store whose unit is the change, with `record`, `log`, `status`, and visibility-filtered export built on it. Git remains the universal interchange format. Every store exports back to byte-identical git history, and GitHub is reached through that exporter rather than through a fork of git. Today the store serves the court; the declared direction is for it to become the source control itself. A change can still arrive from a human on git, an agent on Jujutsu, or a model running in memory, and Oot judges all of them the same way. The project began as a five minute sketch about semantic merge conflicts. The real target is wider: governance over changes, with meaning as one axis among three. ## Why this exists @@ -10,13 +10,14 @@ Agents now write a large share of our merges. A 2026 study of 142,652 AI-agent p The deeper problem is that Git's primitives are the wrong shape for this world. Permissions are repository-level, so keeping one file private means a third-party secret manager and a prayer ([git-crypt](https://github.com/AGWA/git-crypt) exists, but it does encryption, not policy). Branches and pull requests add overhead that tools like [Jujutsu](https://github.com/jj-vcs/jj) have already shown we do not need. And a materialized working tree is a bottleneck: cloning or reinstalling thousands of small files takes 30 to 40 seconds on macOS APFS where Linux does it in 3 to 12. -Oot does not try to replace Git or Jujutsu. It sits above them and above the actor, and it answers the questions they were never built to answer. +Git and Jujutsu track lines. Oot holds history in order to govern it, and it answers the questions they were never built to answer. ## How Oot thinks: changes, not commits -Oot's unit is the **Change**, a content-addressed delta between two snapshots. No branch name, no commit message, no checkout required. From that one idea the rest follows. +Oot's unit is the **Change**, a content-addressed delta between two snapshots. Judging one needs no checkout and no working tree. From that one idea the rest follows. - **Change**. A delta between two snapshots, from anywhere. +- **Store**. Native history in `.oot/`: a bare git object database plus change records whose parents are change ids rather than commit shas. `record`, `log`, `status`, and `export` build on it. - **Visibility**. The governance spine. A policy on paths or branches: `private-to`, `embargo-until`, `public`. This is the `.env`, monorepo-privacy, and private-branch problem, handled as policy rather than cryptography. - **Intent**. What the change claims to mean. Semantic disputes are checked against this. Meaning is one axis, not the whole product. - **Dispute**. A point of disagreement. Either *visibility* (a policy is violated) or *meaning* (two changes diverge in intent). @@ -25,45 +26,48 @@ Oot's unit is the **Change**, a content-addressed delta between two snapshots. N ## How it works -A change arrives. Oot runs its checks and prints a docket. +A change arrives. Oot runs its checks and prints a docket. Real run against this repo's fixtures: ```bash -$ oot adjudicate --change feature/auth-refactor +$ ./target/release/oot adjudicate --change feature/auth-refactor \ + --base fixtures/repo/base --head fixtures/repo/head \ + --visibility fixtures/visibility.toml OOT DOCKET ───────────────────────────────────────── change: feature/auth-refactor - from: jj bookmark @ main - base: main@a3f7c1d - head: feature@b8e2f4a + from: git + base: fixtures/repo/base + head: fixtures/repo/head - meaning: 4 disputes detected - visibility: 1 private path, 1 embargoed until 2026-09-01 - scope: auth flow, token refresh - authors: @kriday, @agent-7 + meaning: 1 disputes detected + visibility: 1 private path(s) - dispute-01: token refresh logic (line 42) [meaning] - dispute-02: error handling (line 87) [meaning] - dispute-03: return type mismatch (line 103) [meaning] - dispute-04: secrets/.env touched by @agent-7 [visibility] + intent: secrets/.env, src/lib.rs + authors: @you - verdict: ▶ ADJUDICATED. 1 requires review, 1 cloaked + dispute-01: both sides changed `login` (src/lib.rs:1) [meaning] + dispute-02: private path secrets/.env touched by @you (secrets/.env) [visibility] + + verdict: ▶ CLOAKED . 1 requires review, cloaked embargo: patch held for maintainers until 2026-09-01 [a]ccept · [r]eject · [d]ocket ``` -If a dispute crosses policy, Oot blocks the change or cloaks the private parts. If the change is a security fix, Oot can hold it under embargo and distribute it quietly to maintainers before the diff goes public, the way the Git project and GitHub Security Advisories already do manually ([OSSF guide](https://github.com/ossf/oss-vulnerability-guide), [Git embargo process](https://www.kernel.org/pub/software/scm/git/docs/howto/coordinate-embargoed-releases.html)). +This run exits 1 because the change touched `secrets/.env`, a private path, so it gets cloaked. Exit code 0 means the verdict was `adjudicated`; every other verdict is nonzero, so CI and agent loops can gate on it. + +If a dispute crosses policy, Oot blocks the change or cloaks the private parts. If the change is a security fix, Oot can hold it under embargo until a date you set in the policy, the way the Git project and GitHub Security Advisories already do manually ([OSSF guide](https://github.com/ossf/oss-vulnerability-guide), [Git embargo process](https://www.kernel.org/pub/software/scm/git/docs/howto/coordinate-embargoed-releases.html)). Detection and enforcement ship today; quietly distributing held patches to maintainers is still on the roadmap. ## Where Oot sits -- **Storage is someone else's job.** Oot reads snapshots from git or Jujutsu. It never owns the repository. +- **Custody, then interchange.** Oot keeps its own history in `.oot/`, a native store whose unit is the change. Git is the export target: an unfiltered export reproduces every original commit SHA, byte for byte (pinned by the round-trip tests), while adapters still read snapshots straight from git and Jujutsu. - **Execution is content-addressed.** The engine takes byte blobs and works on the parse tree. It never assumes a checked-out working tree, so it runs inside an agent's memory isolate and an agent in a worktree cannot hold `main` hostage. - **Cryptography is delegated.** Actual encryption goes to git-crypt or a hosted key service. Oot owns the policy and the gate, not the math. ## Status -Working seed — the engine runs, the docket renders, and git + Jujutsu ingestion are in-memory. Current focus: using Oot to govern Oot's own changes. +Working seed. The engine runs, the docket renders, git and Jujutsu ingestion are in-memory, and the native store ships with import, record, log, status, and export. Current focus: using Oot to govern Oot's own changes. - [x] Change ingestion from git snapshots (in-memory via `git ls-tree`/`cat-file`) and materialized dirs - [x] Jujutsu ingestion (in-memory via `jj file list`/`file show`, revset resolution, first-class conflict detection) @@ -73,19 +77,31 @@ Working seed — the engine runs, the docket renders, and git + Jujutsu ingestio - [x] In-memory execution path (no materialized tree required for git) - [x] Git adapter with 3-way adjudication - [x] Jujutsu adapter with 3-way adjudication (`--source jj`, revsets accepted) +- [x] Native store: `.oot/` holds a bare git object database plus a change-id DAG (parents stored as change ids, not commit shas) +- [x] Import from git: all branches, idempotent via a sha map +- [x] `oot record`: captures the working copy as a native change, refuses no-op records +- [x] `oot log` / `oot status`: `[git]`/`[oot]` provenance tags, offset-aware dates +- [x] Export to git: byte-identical round-trip, including merge commits, binaries, unicode messages, and non-UTC offsets; signatures survive downstream of unrebuilt changes +- [x] Visibility-filtered export: changes touching private paths are withheld, kept trees rebuilt minus those paths, empty results skipped, embargoed stores refuse export entirely, every decision logged to `.oot/export-log.jsonl` Same-named definitions in one file (e.g. a `render` method on two classes) are each tracked separately: diffing matches identical bodies first, then pairs the rest, so only the definition that actually changed is reported. +Deliberate cuts for now: nested ignore rules and negation patterns in pure-Oot projects, tags, signatures downstream of rebuilt history, gc, and more. + ## License -The adjudication runtime, the docket format, and the adapters are MIT licensed. A court that hides its deliberations is not a court, so the gate stays open. +The adjudication runtime, the docket format, the adapters, and the store with its exporter are MIT licensed. A court that hides its deliberations is not a court, so the gate stays open. -## Someday +## Roadmap Deliberately unbuilt. These need users to be worth their cost, and there are none yet. -- **Hosted intent scoring** — a model that checks what a change claims to mean against what it actually does. The structural engine catches *that* code changed; this would catch *what it means*. Needs a server, a model, and someone paying for both. -- **Embargo distribution** — the courier half of embargo: quietly shipping held patches to maintainers before the public diff drops. Needs keyed private channels and maintainer auth. The detection half already ships. +- **Working-copy update.** Materialize a stored change back onto disk, `oot update` style. Planned next; today you read history with `log` and export to git to check anything out. +- **Store-to-court adjudication.** Run the engine straight off stored changes instead of snapshots. Planned. +- **Hosted intent scoring.** A model that checks what a change claims to mean against what it actually does. The structural engine catches *that* code changed; this would catch *what it means*. Needs a server, a model, and someone paying for both. +- **Embargo distribution.** The courier half of embargo: quietly shipping held patches to maintainers before the public diff drops. Needs keyed private channels and maintainer auth. The detection half already ships. +- **Per-subtree signature reuse.** Any filtered export currently rebuilds every commit, so even clean signed commits lose signatures. Reusing unchanged subtrees would keep more of them. +- **Store gc and pruning.** The store grows without bounds today. ## Try it @@ -93,17 +109,27 @@ Deliberately unbuilt. These need users to be worth their cost, and there are non git clone https://github.com/Epoch-AI-Lab/oot.git cd oot cargo build --release -# materialized dirs + +# adjudicate a fixture change (exits 1: the fixture touches secrets/.env) ./target/release/oot adjudicate --change feature/auth-refactor --base fixtures/repo/base --head fixtures/repo/head --visibility fixtures/visibility.toml + # or 3-way git (in-memory, no checkout) ./target/release/oot adjudicate --change feature/auth-refactor --base-ref main --head-ref feature/auth --repo . + # or 3-way jujutsu (revsets welcome) ./target/release/oot adjudicate --source jj --change greet --base-ref 'bookmarks(exact:main)' --head-ref '@-' + +# or skip snapshots entirely: keep history natively in .oot/ +./target/release/oot init +./target/release/oot status +GIT_AUTHOR_NAME=you GIT_AUTHOR_EMAIL=you@example.com ./target/release/oot record -m "first change" +./target/release/oot log +./target/release/oot export --out exported # auto-applies ./visibility.toml when present ``` ## Contribute -We need people who have been burned by the primitives Oot sits above: +We need people who have been burned by repository-level permissions, leaked diffs, and clean merges that ship bugs: - **VCS adapter authors** who know git and Jujutsu internals and can turn snapshots into Changes. - **Policy people** who have run embargoed releases and know where the process leaks. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 5f47092..0000000 --- a/TODO.md +++ /dev/null @@ -1,305 +0,0 @@ -# Known friction - -## ~~Fixture `.env` policy noise~~ RESOLVED 2026-08-21 - -Only paths *touched* by a change are checked against private-path policy now. -Pinned by `test_visibility_policy_only_flags_touched_private_paths`. - -## ~~Binary change detection is lossy~~ RESOLVED 2026-08-22 - -`Snapshot.files` stores raw bytes; text conversion happens only at parse time. -Pinned by `test_cli_distinct_binaries_are_not_collapsed`. - -## ~~Same-named functions tracked by first occurrence only~~ RESOLVED 2026-08-22 - -`FunctionMap` holds every same-named definition and diffing aligns each name -group by content. Pinned by `test_engine_duplicate_function_names_tracked_separately` -and friends. - -## Rename/rename divergence is swallowed - -Base has `f`; ours renames `f -> g`, theirs renames `f -> k`. Both sides -deleted `f`, so it reads as convergent; `g` and `k` read as plain additions. -Merged result silently holds both names. Pre-existing (the old code hit its -convergent-clean branch the same way), but def-level tracking makes a fix -reachable: detect that the removed base defs survive under different names -per side, then emit High. Trigger: first real rename/rename dispute or the -hosted intent-scoring work, whichever comes first. - -## Positional pairing can swap row attribution on count asymmetry - -When a new same-named def lands *above* a modified one -(base 1x `f`, head 2x `f`), leftover pairing reports "changed" at the new -def's row and "added" at the modified one's row. Counts and severity are -right; only line numbers are swapped. Right fix: similarity-based leftover -pairing (edit distance or tree-sitter diff hash) instead of document order. -Trigger: when a docket consumer starts using dispute rows for navigation. - -## Fallback conflict message says "modified" for pure deletion divergence - -When both branches delete different copies of a same-named def, the High -dispute reuses the "both branches modified function `X` differently" -wording (severity is correct, only the verb is off). Right fix: a dedicated -"deleted differently" message variant. Trigger: first docket consumer that -pattern-matches dispute details. - -## ~~Signed commits lose their signatures~~ RESOLVED 2026-08-22 - -Dogfooding on this repo found it immediately: 4 GitHub-signed merge commits -diverged on export, rewriting every downstream SHA. Fix: export now reuses the -original commit object from the store odb whenever every parent exported to -its own original sha (`source_sha` on `ChangeRecord` + identity fast path in -`replay`). Signatures only break downstream of genuinely rebuilt changes — -exactly where step 2's visibility filtering will rebuild anyway. -Pinned by `tests/store_roundtrip_test.rs::test_roundtrip_preserves_extra_commit_headers`. - -# In progress: Oot store + git exporter (dogfooding Oot on Oot) - -Decision (2026-08-22): Oot becomes future source control. Bridge to GitHub is -an exporter, not a fork of git. Storage is a bare git odb inside `.oot/` -(jj-style: model is ours, bytes are git's). - -## DONE — all tests green (`cargo test`: 11 suites, incl. round-trip) - -- `src/store.rs` — `.oot/` store: bare git odb at `.oot/objects.git`, - `changes/.json` records content-addressed via `git hash-object`, - `.oot/map/` for idempotent import, `.oot/refs/`, - append-only `.oot/.index` in import order. -- Parents are stored as **change ids** (not original commit shas) so the DAG - lives in Oot's own address space. -- CLI: `oot init`, `oot import --repo ` (all branches), `oot export --out `. -- Export attaches the store odb via `/.git/objects/info/alternates`. - Because trees/authors/timestamps/offsets/messages/parents are preserved, - git reproduces **byte-identical commit SHAs** — pinned by - `tests/store_roundtrip_test.rs::test_full_roundtrip_preserves_every_commit_sha` - (merge commit, binary blob, unicode message, non-UTC offsets). -- Gotchas already hit (do not rediscover): root commits have empty `%P`; - `git log --pretty=format:` inserts `\n` between entries (records use `\x01`); - `commit-tree`/`hash-object` skip writing objects that already exist via - alternates, which is why update-ref needed the alternates *file*, not just env. - -## NEXT (in order) - -1. ~~Dogfood for real~~ DONE 2026-08-22. -2. ~~Visibility-filtered export~~ DONE 2026-08-22: `oot export` auto-loads - `./visibility.toml` (or `--visibility `); changes touching private - paths are withheld, kept trees are rebuilt minus those paths via recursive - ls-tree/mktree rewriting, children remap to nearest kept ancestors, empty - results skipped, embargoed stores refuse export entirely. Every decision - lands in `.oot/export-log.jsonl`. Export cache is policy-scoped: switching - policies wipes `.oot/export/` mappings. Pinned by - `tests/export_visibility_test.rs` (strip, embargo, empty-skip, cache). - Verified on this repo: faithful export byte-exact; filtered export strips - `fixtures/repo/head/secrets/.env` and logs both touching commits. - Mirror pushes use an explicit empty policy to stay byte-faithful. -3. ~~`oot record`~~ DONE 2026-08-22: `oot record -m [--branch ]` - snapshots the working copy into the store odb (blobs + recursive mktree, - exec bits kept), creates a native change (`source_sha: null`) as child of - the branch head, refuses no-op records. Identity from GIT_AUTHOR_* / - GIT_COMMITTER_* envs then git config. Ignore rules: git check-ignore in - git worktrees; minimal root-.gitignore matcher (names, dirs, `*`) for - pure-Oot projects. Export already handles native changes via the - reconstruction path — pinned end-to-end by `tests/record_test.rs`, - including mixed imported+native history. -4. ~~`oot log` / `oot status`~~ DONE 2026-08-22: status diffs the working - copy against the branch head's tree (content-addressed, no odb pollution); - log walks reachable changes newest-first with `[git]`/`[oot]` provenance - tags and offset-aware dates (pure arithmetic, no date deps). Both share - `resolve_branch` with record. Pinned by `tests/log_status_test.rs`. - -## Deliberate cuts (v1) - -- `record` ignores nested .gitignore files outside the root, negation - (`!pattern`) rules, and `?`/`[]` glob classes. Git worktrees get full - semantics via git itself; only pure-Oot projects see the subset. - -- Tags and annotated tags are not imported/exported. -- Signed commits downstream of a rebuilt change lose their signatures - (reconstruction cannot forge them; SHAs then differ). Unmodified history is - byte-exact — see resolved friction above. -- Any filtered export rebuilds ALL commits (identity fast path off), so even - clean signed commits lose signatures when one taint exists. Per-subtree - reuse is possible later if it matters. -- Filtered exports still share the store odb via alternates: withheld blobs - are unreachable from refs (a push transfers nothing) but resolve locally - until `git repack -a -d` runs in the export. Never hand out a filtered - export directory alongside its `.oot`. -- Commit messages must be valid UTF-8 without `\x00`/`\x01`; violations fail loudly. -- Exported repo reads blobs through alternates; run `git repack -a -d` in the - export once before deleting `.oot` if you want it standalone. -- No gc/pruning in the store yet. - ---- - -# Night run (2026-08-22, Kriday pre-approved everything below - zero questions mode) - -Positioning line APPROVED: "Oot holds history in order to govern it." -Small calls (naming, defaults, wording) are delegated; document each call in -the PR body. Branches stack where noted. Do NOT merge PRs. - -## PR-O1 `fix/rename-rename-dispute` (bases on main, engine files only) - -- Swallow point: the 3-way convergent check fires vacuously on empty - surviving lists (`src/engine/mod.rs` ~line 300). -- Per file, in the all-three-exist branch, stash side-tagged pools BEFORE any - case dispatch: `removed[side] = (name, base_idx, FnDef)` from `gone`, - `added[side] = (name, FnDef)` from `fresh`. This changes no existing - emissions. -- `find_divergent_renames(removed_ours, added_ours, removed_theirs, - added_theirs)`: greedy pair removed-to-added per side on EXACT signature - equality (body with own name blanked) AND new_name != old_name, - consumed-once on both sides; cross-side join on `(base_name, base_idx)`; - fire only when both sides paired AND names differ. -- Emit ONE High dispute per divergently renamed base def. Detail: - "3-way conflict: both branches renamed function `f` differently (`f` -> - `g` in target, `f` -> `k` in incoming)". Location: theirs row of k, - fallback ours row, then 0. No docket schema change. -- Suppress claimed theirs-fresh defs from `pending_added` so k is not also - reported as a Low addition. Ours-side g needs no suppression. -- Convergent rename (same new name both sides) stays silent. Rename-vs-delete - stays silent: pin as documented-gap test. -- Leave a seam (e.g. `rename_score(a, b)` returning an Option) for future - similarity work but DO NOT switch off exact matching. -- Tests: pure rename/rename gives exactly one High with pinned detail string; - delete/delete + coincidental adds unchanged; convergent rename clean; - base {f,h} with ours f->g h->h2 and theirs f->k h->h2 gives exactly two - Highs; rename + body edit on one side pinned as known gap; helper unit - tests for multiplicity/consumed-once/equal-name rejection. Full suite green - including adapters and CLI tests. - -## PR-O2 `docs/positioning` (bases on main, docs only) - -Approved positioning seed paragraph (adapt, keep honest both directions): -"Oot is the court for code: it governs changes - who may see them, what they -mean, and when they may ship. Governance needs custody, so since August 2026 -Oot keeps its own history: a native `.oot/` store whose unit is the change, -with record, log, status, and visibility-filtered export built on it. Git -remains the universal interchange format - every store exports back to -byte-identical git history, and GitHub is reached through that exporter, not -through forking git. Today the store is an implementation detail serving the -court; the declared direction is that it becomes the source control itself." - -- README.md: - - Intro gains the custody story; REPLACE the "does not try to replace Git / - never owns the repository" sentences (most false lines in the repo). - - "Where Oot sits" bullet 1 replaced: Oot keeps its own store; git is the - export target; unfiltered export reproduces byte-identical SHAs. - - "How Oot thinks" gains a Store bullet (native history, parents as change ids). - - Status checklist adds shipped items: native store (.oot/ odb, change-id DAG), - import (idempotent via sha map), record (native changes), log/status - (provenance tags), export (byte-identical roundtrip incl merge/binary/ - unicode/non-UTC offsets, signed headers kept downstream of unrebuilt - changes), visibility-filtered export (withhold, strip trees, remap, - embargo refuses, decisions logged). One honest-limits footnote pointing - at TODO deliberate cuts. - - Try-it gains the native workflow block (init/status/record/log/export) - and labels the showcase invocation as illustrative or swaps runnable - flags (current mock invocation exits 2). - - License section enumerates the store. "Someday" becomes "Roadmap": - move hosted intent scoring + embargo distribution down, add working-copy - update, store-to-court adjudication, per-subtree signature reuse, gc. - - Discipline rule: present tense ONLY for test-pinned facts; everything - else gets today/next/planned markers. Never close an enumeration of - sources with "only". -- CONTRIBUTING.md: adapter framing becomes two-way bridge (git/jj first-class - sources, exporter keeps git interchange); add "Store & exporter - contributors" audience; build order gains storage/exporter marked DONE; - dev setup gains five-command store walkthrough; license list adds store. -- oot-vision-report.html: add SUPERSEDED banner at top ("Superseded - 2026-08-22: Oot now keeps its own store; see TODO.md decision"). No rewrite. -- visibility.toml comments document dual consumers: adjudicate treats touched - private paths as High disputes (cloak + exit 1); export withholds touching - changes, rebuilds trees minus those paths, embargo makes export refuse. -- Cargo.toml description / clap about may shift to match positioning. - -## PR-O3 `feat/store-court` (bases on main; store.rs + new src/court.rs + main.rs) - -- NEVER mutate ChangeRecord / content addressing. Governance lives in sidecars. -- `.oot/dockets/.json`: latest docket. Schema: `{schema: 1, - change, tree, parents, adjudicated_at, policy_key, docket}` (docket is the - existing Docket struct verbatim). Overwrite on re-run. -- `.oot/adjudications.jsonl`: append-only audit, one line per run: - `{epoch, event:"adjudicated", change, verdict, meaning:N, visibility:M, - policy_key}` (mirrors export-log.jsonl style). -- `policy_key`: deterministic hash/fingerprint of MeaningPolicy + - VisibilityPolicy (same trick as the export cache key). -- New Store methods: `read_blob`, snapshot-from-tree (ls-tree -r -z then - cat-file per blob), `resolve_change(id-or-prefix)` failing loudly with - candidate list on ambiguous prefixes. -- `src/court.rs`: head = change's tree; base = FIRST parent tree (root change - = empty snapshot); authors from record; run engine + visibility + - finalize_adjudication. Merge changes diff against first parent in v1. - Provenance tag `[oot]` vs `[git]` by `source_sha`. -- CLI: `oot adjudicate --change ` engages ONLY when a store opens, - the id resolves, and none of --base/--head/--base-ref/--head-ref/--source/ - --repo/--docket were given (existing modes untouched). Persist by default, - `--no-save` opt-out. Exit codes unchanged (0 only Adjudicated). -- `oot docket ` renders the persisted docket. -- Export coupling stays loose: dockets reference ids, the DAG never references - dockets. No export gating in this PR. -- `oot record` prints a hint line "next: oot adjudicate --change ". -- Tests (`tests/adjudicate_store_test.rs`): happy root change exit 0; - meaning dispute + block_on review means BLOCKED exit 1; child change shows - delta not whole-tree noise; persistence + overwrite + exactly one jsonl - line per run; imported mid-history change carries [git] tag; mixed - imported+native history; unknown id / ambiguous prefix loud errors; unit - tests for read_blob/snapshot_from_tree/save-load/policy_key. - -## PR-O4 `feat/oot-update` (STACKED on PR-O3 branch) - -- PHASE 0 REQUIRED FIRST: `Store::tree_entries(tree)` keeping FULL modes - (100644, 100755, symlink 120000, gitlink 160000); keep existing - `tree_files()` as wrapper so status/export call sites are untouched. - Reject non-UTF-8 paths loudly. (Today modes are silently dropped - that - corrupts materialized trees.) -- `.oot/HEAD`: one line, `ref: ` or bare change id (detached). - `init` writes `ref: main`. Missing HEAD file = legacy resolve_branch - behavior so existing stores/tests pass. Once present, record/status/log - default through HEAD (fixes multi-branch papercut). -- Command: `oot update [--branch ] [--change ] [--dry-run] - [--force]`. No args materializes current branch head (legacy fallback when - no HEAD). `--change ID` = detached materialize. -- Dirty semantics: REFUSE (no stash in v1) when tracked paths are - modified/deleted vs the source tree OR an untracked file would be - overwritten with different content. Factor status's delta computation into - shared `worktree_deltas(store, root, tree)` so status and update cannot - drift. Untracked-but-untouched files are fine. Error lists offending paths, - suggests record-first or --force. -- Fast path: same tree = "up to date", bookkeeping only. Per-file: compare - blob_sha against on-disk file, rewrite only mismatches (converges, avoids - mtime churn). -- Apply order: write-new-content, apply-deletions, flip HEAD LAST. An - interrupted update is repaired by rerunning (idempotent crash recovery). -- Materialization: single `git cat-file --batch` process; temp+rename atomic - writes; exec bits restored; deletions come ONLY from old-tree minus - new-tree path set (never filesystem scans); prune now-empty parent dirs - best-effort; symlinks materialized with std::os::unix::fs::symlink - refusing absolute or ..-escaping targets (record still skips them - - document asymmetry); gitlinks skipped with warning; empty dirs - unrecoverable, documented. -- Path safety BEFORE any disk write: reject absolute paths, `..` components, - `.git`/`.oot`/`.jj` components, case-insensitive-FS collisions. All git - plumbing with explicit --git-dir. -- `--dry-run` prints planned A/M/D + ref moves, touches nothing. `--force` - overrides refusals with printed data-loss warning. Update never consults - .gitignore and never gates on visibility.toml (store-local operation). -- Tests (`tests/update_test.rs`): restore roundtrip bytes + exec bits; - up-to-date no-op; branch switch removes stray tracked file, keeps - unrelated untracked; dirty refusal nonzero + paths listed + disk - unchanged; --force proceeds; untracked collision refused; empty dir - pruned; dry-run zero mutations; HEAD bookkeeping incl. record defaulting - to HEAD branch in multi-branch store (previously error); detached - semantics (status works, record refuses); imported repo wipe-then-update - byte-equal (binary + unicode name + merge); malicious ../escape crafted - tree fixture fails loudly writing nothing outside root; half-applied state - converges. Unit tests: tree_entries modes/kinds, path-safety validator, - deltas. Audit log_status_test.rs expectations against HEAD changes. - -## Night-run status - -- [x] PR-O1 fix/rename-rename-dispute -- [ ] PR-O2 docs/positioning -- [x] PR-O3 feat/store-court -- [ ] PR-O4 feat/oot-update - -Mark your PR's box `[x]` in the same branch before opening it. diff --git a/oot-vision-report.html b/oot-vision-report.html index 206111f..219283f 100644 --- a/oot-vision-report.html +++ b/oot-vision-report.html @@ -153,6 +153,9 @@ +
+ Superseded 2026-08-22: Oot now keeps its own store; see the README for where things stand. +
diff --git a/visibility.toml b/visibility.toml index 9953128..6c6bd72 100644 --- a/visibility.toml +++ b/visibility.toml @@ -1,8 +1,16 @@ # Oot governing Oot. # -# Path fragments (substring match) that must never appear in a change's -# head snapshot. Touched paths raise High-severity visibility disputes, -# which cloak the change and exit nonzero. +# This one file feeds two consumers: +# +# - `oot adjudicate`: a change whose head snapshot touches a path below +# raises a High-severity visibility dispute. The change is cloaked and +# the run exits nonzero. +# - `oot export`: changes touching these paths are withheld entirely, kept +# trees are rebuilt minus those paths, and an active embargo schedule +# makes export refuse outright. Every decision lands in +# `.oot/export-log.jsonl`. +# +# Path fragments are matched by substring against touched paths. private_paths = [".env", "secrets/", ".pem"] # No embargo schedule for this repo yet; add `embargo_until = "YYYY-MM-DD"`