Skip to content

okf: path-scoped ingest + status porcelain (ADR-001/002) - #57

Open
indexzero wants to merge 2 commits into
feat/okf-cli-honesty-adr-003-006from
feat/okf-concurrent-writer-adr-001-002
Open

okf: path-scoped ingest + status porcelain (ADR-001/002)#57
indexzero wants to merge 2 commits into
feat/okf-cli-honesty-adr-003-006from
feat/okf-concurrent-writer-adr-001-002

Conversation

@indexzero

Copy link
Copy Markdown
Owner

What

PR 3 of the ADR stack (base: #56, which bases on #54). The two concurrent-writer features, one commit each:

  • ADR-001 — path-scoped ingest: auto-okf ingest <vault> <bundle> [paths…] restricts the scan to the named bundle-relative paths. Nonexistent, outside-root, directory, or non-.md named paths error by name with exit 1 and zero ops appended — a mixed good+bad argument set aborts all-or-nothing (silent skips of explicitly named paths are the confusion these ADRs exist to kill). Zero-path form unchanged. Literal paths, not an in-process glob engine: the shell already expands globs, and a second subtly-different glob dialect is a new confusion source.
  • ADR-002 — auto-okf status <vault> <bundle> [--json]: read-only per-file porcelain with a fixed five-state vocabulary — in-log, pending-ingest, dirty, absent, foreign — derived from the same projection plan materialize uses (a planBundle extraction from faces/lib/materialize.js, so status can never disagree with what materialize would do). --json is a sorted array of exactly { path, state }; human form is <state>\t<path> lines matching the wanted/flags style. Appends no ops, writes no files, touches no manifest — asserted byte-for-byte in tests.

The headline regression test is the confusion log's own incident, now pinned: a file swept into the log by a separate spawned CLI process reports in-log, not pending-ingest — the case that previously required reading ingest.js source to diagnose.

Verification

  • pnpm test:okf → 135/135; pnpm test (full repo) → 250/250; cli.test.js 13 → 18 tests
  • ADR-001: selectivity (only the named file's ops land; the other file still reports pending via materialize exit 3), refusal-by-name with store-hash proof of zero ops, flag-last invocation forms still parse (paparam greedy-rest compensated in cmdIngest)
  • ADR-002: the swept-writer scenario, foreign-file no-ingest (view hash + manifest bytes + file all unchanged after status), --json byte-stable across consecutive runs, dirty and absent covered

Notes for review

  • The pending-ingest/dirty tiebreak follows the ingest watcher's own semantics (documented in the commit body): dirty = manifest-tracked but diverged from the current projection; pending-ingest = untracked file the watcher would establish (same non-empty-type gate); foreign = untracked and not establishable. Derived faces (index.md/log.md) participate as in-log/absent/dirty.
  • Flag-queue state is deliberately not folded into status (ADR-002 defers it — one command, one question).

indexzero and others added 2 commits August 1, 2026 01:14
…set (ADR-001)

auto-okf ingest <vault> <bundle> [paths…]: optional trailing
bundle-relative paths restrict the one-shot scan to exactly those concept
files, so one agent's ingest never sweeps up another writer's mid-write
pending file. All named paths are validated before any op is appended
(all-or-nothing): a path that does not exist, resolves outside the bundle
root, or is not a .md file exits 1 naming the path, zero ops appended.
Zero paths = full scan, unchanged.

Judgment calls: literal paths only — the ADR permits globs, but paparam's
rest capture is plain strings and the shell already expands globs, so an
in-process matcher would only add a second, subtly different glob dialect;
a named non-.md path (or directory) errors rather than being silently
skipped, since silent skips of explicitly named paths are the confusion
this ADR exists to kill. paparam's rest is greedy (once both positionals
fill, remaining flags land in rest), so cmdIngest re-recognizes
--confirm-deletes/--help there to keep the pre-ADR flag-last forms
working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in (ADR-002)

auto-okf status <vault> <bundle> [--json]: one <state>-TAB-<path> line per
.md file in the union of {planned projection, emission manifest, disk},
sorted by path; --json emits the same rows as a stable [{ path, state }]
array (the schema is contract, kept to exactly those two fields). Strictly
read-only: no ops appended, no files written, no manifest touched — status
never even mkdirs a missing bundle dir (the new faces status() skips
rootRegistry for that reason).

The states are judged against the exact bytes materialize would write:
materialize's plan steps 1-4 are extracted into planBundle(vault), shared
by materialize and status, so nothing is reinvented. Mapping, from the
strictest reading of the ADR table plus the ingest watcher's own
classification as the pending-ingest/dirty tiebreaker:

- in-log:  on-disk bytes hash-match the current planned projection.
- dirty:   manifest-tracked (so the concept is in the log) but bytes
  diverge from the projection — either a hand edit the watcher would fold
  in as diffs against the existing concept (it hashes against the
  manifest, not the plan), or a log that moved past the last materialize.
- pending-ingest: untracked hand-authored file the watcher WOULD establish
  a concept for (same non-empty-type gate as IngestWatcher._fmType).
- absent:  planned but missing from disk (materialize would restore).
- foreign: untracked and not establishable (e.g. a mid-write WIP with no
  parseable type) — ingest will not put it in the log as it stands.

Derived faces (index.md/log.md) are part of the projection and report
in-log/absent/dirty like any planned file; a manifest entry with neither a
file nor a projection is retired state and gets no row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant