Repository navigation
Commits name their writer: author and an Okfy-Actor trailer - #8
Closed
theaspirational wants to merge 19 commits into
Closed
theaspirational wants to merge 19 commits into
theaspirational wants to merge 19 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
stdio is one process per agent session on one machine. okfy-mcp serve --http is one long-running process on a host, serving the same seven tools to every client that holds the token, each seeing the others' writes on its next call. The token is read from a file, never a flag; missing, unreadable or short refuses to start (E_MCP_HTTP_TOKEN). A request without the header is 401 with the documented envelope (E_MCP_UNAUTHORIZED) before the MCP layer sees it. Stateless, JSON responses, no Session trace: one process serves everyone, so observed is absent rather than attesting one client's reads to another's write. Plain HTTP on loopback by default; TLS and anything finer than one token are a reverse proxy's job. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
…e actor `--http --oidc-issuer <url> --oidc-audience <client id>` is the second access model next to the shared file token: every request carries a JWT from one OpenID issuer, checked against its published keys (RS256, iss, aud, exp). Discovery is read once at start, so a wrong issuer refuses to start (E_MCP_OIDC_ISSUER) instead of failing every request. The MCP SDK's own middleware answers 401/403 and publishes the protected-resource metadata under --public-url, so an MCP client can find where to log in. Under a token, okfy_write and okfy_propose record the token's `sub` as the actor and ignore the client's own claim: the first place an actor string is a verified identity rather than a declaration. `okfy:read` is needed to call anything, `okfy:write` for the two writing tools (E_MCP_FORBIDDEN, nothing written), so a read-only guest is a token without the scope. Tested against a stub issuer that mints real RS256 tokens: wrong issuer, wrong audience, expired, unknown key and the old shared token never reach a tool; a read-only token searches but cannot write; the ledger names the signed subject, not the typed actor. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
…actor) The SDK publishes `required_scopes` as the resource's `scopes_supported`, so a client that reads the metadata (as the MCP spec says to) would ask for okfy:read only and could never write. The protected-resource document is now served by okfy-mcp itself: okfy:read, okfy:write and offline_access advertised, okfy:read alone required to call. An issuer that serves several agent apps for one human can set an `okfy_actor` claim (`claude:vasyl`, `codex:vasyl`); it wins over `sub` as the ledger actor, so the apps are told apart while the identity is still the issuer's word, not the client's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
PyJWKClient fetches the issuer's keys with urllib's stock User-Agent, which Cloudflare in front of the issuer answers 403, so every token was refused while the discovery read (httpx) at start had passed. The verifier now sends `User-Agent: okfy-mcp`, and a refused token logs its reason (never the token) at warning level: without that line the 401 had no clue in it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
The SDK's DNS-rebinding guard allows only localhost Host headers by
default, so every request through nginx ("Host: memory.example") was
answered "Invalid Host header" after a valid token. `--public-url` now
applies to both HTTP modes and its host is allowed, next to loopback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
An Authentik puts each registered client app under its own issuer path, and one bundle serves Claude, Claude Code and Codex alike. --oidc-issuer now repeats; the protected-resource metadata lists every issuer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
…s scope The scope says what the client may do for the user; the group says what the user may do. A guest whose client asked for okfy:write still reads only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
…d itself MCP clients that find no client id register themselves with the issuer; an Authentik then puts each under its own issuer path with a client id nobody knew in advance. Under a trusted prefix, discovery is read on first sight, the signature checked, the audience not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019a1Mcx6dNbaNqkq9dmYVgf
…to its own people The write group said who may write; nothing said who may read, so any token the issuer signed could read any bundle it served. With a read group, a user the issuer does not list in it has every scope withheld and is answered 403, nothing served; inside it the write group still decides the writes. A friend's bundle and a shared one can now run next to the owner's behind the same login. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every sanctioned commit that writes a ledger row passes the row's actor to _commit. The message gets an `Okfy-Actor:` trailer; for a non-human actor the commit author is the actor too, so git log, shortlog and author-grouping tools tell agents apart without parsing the message. The owner (`human:` actor) keeps their own git identity as author. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
theaspirational
force-pushed
the
pr/commit-actor
branch
from
October 16, 2026 08:30
8e864e4 to
1818d62
Compare
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>
This was referenced Oct 16, 2026
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 #7 (and through it on #6, #4 and #5). The diff shows the lower commits until they merge; merge in order and nothing conflicts. This PR's own change is the last commit.
A hub that serves many agents commits every write under one git identity, the server's. The writer was recorded, but only in
meta/memory.jsonlandlog.md, sogit log, GitHub's graphs and any tool that groups by author saw one writer for everything. Putting the name into the message subject would make every reader parse free text. This puts it into the two fields git itself parses.What changes
_committakes an optionalactor. Every call that writes a ledger row passes that row's actor: accept (the proposal's author for content, the owner for flag, gap and delete), reject, dismiss, refine and the direct-write lane.Okfy-Actor: <actor>, read back exactly withgit log --format='%(trailers:key=Okfy-Actor,valueonly)'.human:one, the commit's author is the actor:claude:vasyl <claude-vasyl@okfy.invalid>. The committer stays whoever runs the process. That is git's own meaning of the two fields: who wrote the change, who stored it.human:actor is the owner working through their own git identity, which stays the author._rewrite_meta, eval, dissent, ledger runs,okfy package) are unchanged.Not changed
Tests
🤖 Generated with Claude Code