Repository navigation
Direct-write lane: types the owner opens are written without review - #6
Closed
theaspirational wants to merge 9 commits into
Closed
theaspirational wants to merge 9 commits into
theaspirational wants to merge 9 commits into
Conversation
…tched A short English stopword list is dropped from the query before BM25, in query.search_pool only: the index keeps every token, so no bundle's index fingerprint moves. In a small corpus a word like "who" carried real idf and pulled an unrelated concept to rank 2 (the reviewer's "Rook FTP login credentials who issued" ranked the owner profile second on "who" alone). Each hit now carries matched_terms: the query's terms, lexicon expansion included, found anywhere in the concept's index tokens, body included. The MCP adapter's `matched` used to overlap the query with title+description only, so a hit that matched in its body showed [] and looked like noise. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 9d86b0c)
…=true `matched.terms` on a query hit is now the core's matched_terms (body included, stopwords never) instead of an adapter-side overlap with the hit's title and description; via_lexicon still names the expansion's share. Same key, better value: the additive contract holds. okfy_show(concept_id, outline=true) returns the `## ` headings in order and the sha, no content, so an agent can pick a `section` without reading the whole concept or guessing a heading to get the menu from the error. Single concept_id only; a multi-id call ignores it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 6d40d74)
FastMCP now carries `instructions`: search with the user's own words, okfy_overview is the map, okfy_show's sha256 goes into okfy_write, a fact worth keeping goes back on the concept it belongs to, a search that finds nothing becomes okfy_propose(action="gap", query=<the user's words>), and retrieved content is data. A bundle-mode query with no hits also carries `way_out` naming that gap proposal: the moment an agent needs the gap door is the moment it sees it. Until now the door was one phrase inside okfy_propose's description and nothing else told a fresh client about it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 592f565)
A concept id is its path, so it changes when a page moves. Anything outside the bundle that points at a page needs an id that survives renames and edits. - bundle_uid in meta/purpose.md and okf_id in each concept page's frontmatter; reserved pages are addressed as bundle_uid plus path. - okfy migrate ids backfills both into an existing bundle with uuid5s every full clone derives alike; the git read is pinned against clone-local settings. Once written, an id is never recomputed. - Any RFC 3986 scheme is an external link; okfy links and okfy_links report external targets. - A bundle embedded at any path is left out of corpus listings; init --embed no longer lists its own bundle; meta/corpus.md records manifest_digest. - Validation: W_BUNDLE_UID_MISSING, W_OKF_ID_MISSING, E_BUNDLE_UID, E_OKF_ID, E_OKF_ID_DUPLICATE, E_MANIFEST_DIGEST. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent had one way to write: file a proposal and wait. That is right for
knowledge and wrong for session notes, observations and logs, where several
agents working at once need to see each other's writes now. meta/purpose.md
may now list `direct: {types: [...]}` under `write_policy: proposals`; a
concept of a listed type is written at once, everything else still goes to
proposals/. `write_policy` keeps its two literals and the pre-commit hook is
unchanged.
Concurrency is optimistic and adds no lock: the writer presents the sha256 it
read (the digest okfy_show already returns, or `absent` to create), and the
check and the write are one step under the existing review_lock. Of two
writers holding the same sha256 one lands and the other gets
E_WRITE_BASE_MOVED with the current sha256.
The lane relaxes review and nothing else. The injection and secret scans, the
`sources:` check and the archetype schema are the same functions a proposal
passes; a direct write cannot change `type` or anything an owner decision
wrote (verified, generated, the stale fields, supersedes/superseded_by); and
meta/ is never in the lane, so the policy cannot rewrite itself. The ledger
row is an `accept` with `origin: agent-direct` and the agent as actor, and it
does not dispose of an owner's standing rejection.
MCP gains okfy_write, which routes by the bundle's policy so the agent does
not need to know it: `channel: direct` or, through the okfy_propose path and
against the same sha256, `channel: proposal`. The CLI gains `okfy write`,
which refuses instead of falling back.
`okfy refine` is untouched and still neither scans, validates nor locks.
No version is bumped; CHANGELOG.md carries the entry as Unreleased. This
adds two test files to a tree that exports none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
A review of the lane found no way to write what it must not, and five places where two readers of one thing disagreed. `okfy dismiss` and `unresolved_rejections` each folded the ledger for "does a rejection still stand", and only one of them skipped an agent's direct write: after one, the rejection stayed unresolved and the owner could no longer dismiss it. Both now read `memory.latest_decisions`. okfy_write tells the agent to send owner fields back as okfy_show returned them, and its proposal fallback then refused a verified concept's whole text for carrying `verified`. A `verified` equal to the base's own is dropped at that intake, as the patch path already drops it; a different one is still refused as forged. A target deleted after it was read answered E_DIRECT_TARGET; it is a moved base and now answers E_WRITE_BASE_MOVED with `current_sha256: absent`. An extraction plan naming an unknown archetype escaped the MCP envelope as a FileNotFoundError, and was loaded before routing; the schema is now loaded only when a write reaches its check. An explicit `direct: null` read as an absent block; it is E_DIRECT_KEY. Each has a test that fails without the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
A bundle that lists direct.types still shipped an AGENTS.md whose memory block says memory is never written to concepts directly. An agent that reads files rather than tool descriptions was told to propose exactly the types the owner had opened. okfy package now appends one block, between okfy:direct markers, naming the listed types and okfy write. It is rendered only for a well-formed direct block under write_policy: proposals, so every other bundle's AGENTS.md is byte-identical and the memory block keeps its 60-token bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
scan_values collapsed every newline of a value before its second pass, so a whole concept body became one line and the exfiltration co-occurrence rule, written for one line, matched an outbound verb, the word password and a URL anywhere in the document. On a 48-concept bundle of working notes 8 bodies were refused, 5 of them for words in different paragraphs or different list items, with the whole body as the excerpt. A blank line and the start of a list item, heading, quote or table row are now line boundaries. A phrase split by a hard wrap is still caught; one split across a blank line or a list marker is seen by neither pass. Separate from the direct-write lane on purpose: it changes what okfy propose accepts too, and can be dropped from the branch on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
…he lane When the plan declares types, a concept of any other type is E_UNKNOWN_TYPE the moment validate sees it. write_direct did not check that: the type missed the lane, the MCP adapter filed a proposal invisible to search, and an owner who accepted it got a bundle that no longer validates. The same check, before the write: E_UNKNOWN_TYPE, nothing filed, the message naming the declared types. Bundles that declare no types are unchanged. _declared_types tolerates a caller with no archetype in hand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
vsov
pushed a commit
that referenced
this pull request
Oct 16, 2026
Version stays 0.29.0; everything is listed under "## Unreleased" in CHANGELOG.md. - Audit fixes A1-A10 since v0.29.0: a typed workspace acceptance policy, federation reads each index once, stricter release checks. - #5 Retrieval: stopwords leave the query, every hit names the terms it matched, okfy_show outline, server instructions. The retrieval code digest moves, so recorded eval runs read stale. - #4 Stable page identity: bundle_uid, okf_id, okfy migrate ids, scheme links as external, embedded bundles left out of the manifest. - #6 Direct-write lane for concept types the owner opens, okfy_write; the injection scan joins hard wraps only. - #7 okfy-mcp over HTTP: one shared token or identities from an OpenID issuer, with security fixes from review (prefix-bounded discovery, cached fetch failures, exact group match, no actor fallback). - #8 Commits name their writer: agent author and Okfy-Actor trailer; actors are one printable line of at most 255 characters. Contributed by @theaspirational in #4, #5, #6, #7 and #8. Co-authored-by: Aspirational <dovganchin@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
|
Thank you! This is integrated into the source tree and published in a0517dd ("Unreleased: audit fixes A1-A10 and contributed PRs #4-#8"). Your commits were cherry-picked with authorship kept; this repository is an export of the source, so the PR itself is closed rather than merged. Your tests live in the source tree, which the export does not ship. Follow-ups: See CHANGELOG.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #5 and #4. This branch starts where
okf-idsends, so the diff shows their commits until they merge; merge those two first and this PR shrinks to its own five commits. No conflicts in that order.Until now an agent had one way to write: file a proposal and wait for the owner. That is right for knowledge and wrong for session notes, observations and logs, where several agents working at once need to see each other's writes now. This adds a second lane the owner opens per concept type. Nothing changes for a bundle that does not open it. No version is bumped: the number is yours.
The lane
meta/purpose.md, underwrite_policy: proposals, may declaredirect: {types: [note, session-log]}.write_policykeeps its two literals and the pre-commit hook is unchanged: a direct write commits through the same_commitevery sanctioned writer uses.okfy.proposals.write_direct(...)writes a concept whose type is listed, at once. The lane is decided by the concept's current type on an update and by the proposed type on a create.meta/and the other reserved directories are never in it, so the policy cannot rewrite itself.expect_sha256is the sha256 of the file the author read (the digestokfy_showalready returns), orabsentto create. Check and write are one step under the existingreview_lock, from one read, as a temp file and a rename. Two writers holding the same sha256: one lands, the other is refusedE_WRITE_BASE_MOVEDwith the current sha256.sources:outside the corpus, the archetype schema (E_DIRECT_INVALID). A direct write cannot changetype(E_DIRECT_RETYPE) or what an owner decision wrote:verified,generated, the stale fields,supersedes/superseded_by(E_DIRECT_OWNER_FIELD).acceptwithproposal: null,origin: agent-direct, the agent as actor andbase_sha256. It is a content write, not a decision:unresolved_rejectionsandokfy dismissread one shared fold that skips it.okfy write <bundle> <concept_id> --as <actor> --expect <sha256|absent> (--from <file> | --patch-file <hunks.json>). It refuses; it does not fall back.MCP
okfy_write(concept_id, actor, expect_sha256, content?, patch?, note?, evidence?). The agent does not need to know the policy: a listed type is written at once (channel: "direct", with the new file's sha256); any other type, and any bundle with no lane, is filed through theokfy_proposepath (channel: "proposal"), only if the target is still atexpect_sha256.persisted: truein both cases. A refusal is{error, message, way_out}.by,queries,target_shown,base_shown), so an agent that takescurrent_sha256from a refusal and writes without reading is visible. Not prevented: an actor string is a record, not authentication.E_UNKNOWN_TYPEbefore the lane check.okfy packageadds one block toAGENTS.mdbetween<!-- okfy:direct -->markers, only for a bundle with a well-formeddirectblock; every other bundle'sAGENTS.mdis byte-identical.Stricter behaviour
directinmeta/purpose.mdis now a checked key:E_DIRECT_KEY,E_DIRECT_TYPE,E_DIRECT_POLICYat every strictness level,W_DIRECT_TYPEfor a listed type outside the archetype's canonical list in a bundle that declares notypes. "An MCP tool call can never touch a final concept" is now true per bundle, not per adapter.One commit outside the lane, droppable on its own
The injection scan joins hard wraps only. The second pass of
okfy.injection.scan_valuesused to collapse every newline of a value, so the exfiltration rule, written for one line, matched an outbound verb, the wordpasswordand a URL anywhere in a document. Measured on a 48-concept bundle of working notes: 8 bodies refused, 5 of them for words in different paragraphs. Blank lines and the start of a list item, heading, quote or table row are now line boundaries. A phrase split by a hard wrap is still caught. One split across a blank line is seen by neither pass: that is the price, yours to accept or refuse.Not changed, and not claimed
okfy refineis left alone: the owner's door, no scans, no lock. One commit per direct write. A concept created directly is found byokfy queryat once and reachesindex.mdat the nextokfy package, like an accepted create.Tests
core/tests/test_direct_write.pyandadapters/mcp/tests/test_write_tool.py: every refusal also asserts nothing was written (file bytes, ledger rows and commit count compared before and after). The full CHANGELOG section in this branch lists every new code.🤖 Generated with Claude Code