Release 0.78.0 — the forest keeps what it is handed - #21
Merged
Merged
Conversation
… behind Canopy's module docstring said "Pure-Python, stdlib only (no numpy): the forest is small ... so a flat scan over a few thousand 1024-d vectors is trivially fast". That was true when it was written. At 1,877 nodes the scan measured 67.3 ms per locate — the same 68 ms J.10.4.1 named dense_ms after and prescribed an index for — and the prescription was wrong for this corpus. The arithmetic is 1.9 M multiply-adds, which is 35 ns each: not a corpus that outgrew a flat scan, a flat scan written one Python float at a time. Worse, save() had already written vectors.f32 as one contiguous float32 block, the exact layout the scan wants, and load() spent 49 ms taking it apart into 1,877 Python lists. The file was right and the reader undid it. So this deletes more than it adds: load reads the block, search is a matmul. Measured 67.3 -> 0.517 ms and 49.2 -> 2.74 ms, store 77 -> 15 MB, and numpy wins at every size tested from N=5 up — there is no small-forest crossover to guard. In memory the store is float64, on disk it stays float32 and byte- identical. The accumulator is what that decides, and the code being replaced summed Python floats, so float64 reproduces the ranking rather than approximating it: against that older calculation float32 drifts 2.04e-08 and float64 drifts 5.55e-17, while the tightest gap between neighbouring hits across six real bge-m3 indexes is 5.51e-07. A 27x margin is not one to ship a silent reordering on for 4x on a scan that is already 130x faster. All six of those indexes load and rank identically, as does the whole hybrid locate end to end on two forests. numpy is the optional monkeyllm[canopy] extra and the engine's own three dependencies are unchanged; without it the Python scan runs exactly as before, and a test compares the two paths, their bytes, and their behaviour across upsert and remove. It resolves on first use and never at import — v0.62's rule about the fold table, applied to a dependency, since `import numpy` costs 32.2 ms of start-up and a plant, a validate or a BM25-only forest must not pay it for a layer they never touch. Then the part that was not about speed. struct.unpack refused a buffer that was not an exact multiple of the vector width; np.frombuffer takes the whole vectors and drops the remainder. A half-written vectors.f32 would have loaded with ids longer than vectors, and the frontier ranking's zip drops the unpaired tail, so those nodes score -1.0 — last, forever, with nothing saying why. Both stores refuse it now, and the check covers the id-count half too, which was silent in both from the start. That refusal then exposed a defect older than any of this. Vine.__init__ acquires the writer lock and the release lives in close(), so a raise between them held the lock forever: the operator did exactly what the error asked, rebuilt the canopy, and found the forest still refusing — now blaming a live writer that did not exist. The lock is released if construction fails. catalog.reindex() sits in the same block, so the symlink gap pinned in test_reindex_meets_a_symlink stops leaking too. Releasing it correctly uncovered what it had been covering: over the API a forest that cannot open is byte-identical to a cold one, and /v1/health reports ok. Boot now names it on stderr with the file and the repair, on the same station: channel as the other boot refusals — that half needed no contract. The API half does: closing it means a new canopy_status.state, which K.4 enumerates, or serving a boot report nothing serves today. It is pinned by a passing test in the shape the symlink gap uses, not endorsed. Spec debt this leaves, for the next cut: J.10.4.1 states 68 ms as a measured fact and prescribes an index for whoever dense_ms dominates; the float64 accumulator is a ranking property that belongs in the contract rather than in a constant somebody can tune back; and the corrupt-index blast radius wants deciding, since the dense layer is optional and _derived/ is disposable, so degrading to BM25-only is the proportionate answer this could not take without adding contract. Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
`locate` searches curated metadata and nothing else, and the Curator authors half of it under a rule that was quietly emptying it: tags were validated against `^[a-z0-9][a-z0-9_-]*$`, so in a Portuguese forest `produção`, `segurança` and `orçamento` were dropped on their way to the passport — no error, no count, no line in any report. The operator watching that sees a model that will not write tags, and goes to tune a model that was doing its job. The rule bought nothing. The FTS row is tokenized `unicode61 remove_diacritics 2`, which folds on the way in AND on the way out: a tag stored as `produção` is matched by a query for `producao` (measured). The accent was a matching concern written into an authoring rule, where it became a content filter that only bites forests that are not in English. The same measurement cleared the identifiers — `be-291`, `p95` and `iso-27001` always passed the regex and always survived the tokenizer, so the only thing between a document and its own ticket number was the prompt's "single words" beside a cap of five. v0.52 taught the READER to keep code-shaped tokens; the writer was still told to throw them away. Spec v0.75: the Curator writes a findable scent, counted when clipped and never silently dropped (G.4.2), and proposes aliases the document itself contains (G.4.3); a node MAY state its language, and absence means nobody said — never a guess (A.3.2); a person can confirm or reject an uncertain link, because heat can delete a cold proposal and can never say yes to one (H.2.1, J.18); re-curation MAY use the forest's ingest model, as a job with the bill stated before it is spent (J.13.6.1); and the operator finally holds the hands the connected agent always had — prune, transplant, tags edited, applied in bulk and browsed (J.5.17, J.5.18). Acceptance F.162-F.169. Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
The v0.75 implementation, engine to console. One tag rule in models.py — Unicode categories, not an ASCII range, so `produção` and every Indic script survive; NFC+casefold decides uniqueness and never respells; every refusal counted (`tags_dropped`) where `aliases_clipped` already travels. Cap 12. The prompt asks for what a technical corpus is searched by: identifiers, codes, proper names, categories, patterns. Enforced at plant/graft with exact-spelling grandfathering, so a pre-v0.75 node still opens and still round-trips. The Curator proposes aliases guarded by occurrence in the body under the one exported fold — an invented acronym is refused by the check, not the prompt — and the union never displaces a hand-written alias. `lang` is a fact or it is absent: BCP-47-shaped, exact match, a filter where candidates are chosen and never a boost, counted by coverage, cleared by null like origin. No stage guesses one — a model is not a source. `yaml.safe_load` reads `lang: no` as False and `no` is Norwegian; a before-validator maps it back. `derive: ["scent"]` re-curates an already-ingested forest with its own model, as a job whose bill is stated before it is spent: summary replaced, tags and aliases unioned, only what the Gardener planted, branches left to the rollup, datasets curated from their map, the principal trailer set per step. The vote settles what heat cannot — under the shipped defaults a cold 0.3 proposal is exactly what the Ranger's cold-prune deletes, so heat could kill a proposal and never confirm one. links.py owns the audited rewrite and the Ranger's own path is now a wrapper over it. Accept writes 1.0 and leaves the managed population; reject removes; a batch of fifty is fifty decisions, not one. No MCP tool can cast a vote, and the tests assert the absence. Consoles: prune and transplant with E_ANCHORED rendered as navigable anchors and force as a second decision; the Links review entered from the Health count it explains; scent editable where it is read; bulk tags that union and never overwrite; the vocabulary counted via json_each over the stored arrays — never the FTS row, which would report the folded spelling. ScopedVine and the MCP surface learned the new parameters, because the Station enumerates what it forwards. Suite 1653 green. F.169's bench measurement against bench/questions-locate-v1.json (baseline recall@1 0.711) needs the local model and is the round's stated remainder. Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
Explore's docked timeline was a scrubber with a single thumb: drag it back and the forest stands as it stood on that day, press play and it grows again. That answers what existed by then. The question a forest asks once it is being fed is what arrived BETWEEN two days — this week's ingest, the batch somebody ran on Tuesday — and one thumb cannot say it: every node older than the day is in the picture, so the new material is a needle in the haystack it just joined. On a 1,878-node forest "what was planted last week" is forty dots among eighteen hundred. The obvious repair is a filter, and a filter is where it goes wrong. Hide the nodes outside the window the way the replay hides the unborn — out of the picture AND out of the physics — and the survivors are leaves whose branch was planted a year earlier: no spring left, and the centring pull folds the constellation into the middle of the canvas. The picture then answers "what" and lies about "where", and where the material of those days landed is the reason to draw it on a map at all. Spec v0.76: J.5.4 gains the window. A start beside the end on one `created` scale, a node shown iff its rank lies in [start, end); the start at its origin is the v0.38 scrubber unchanged; a node outside the window leaves the picture and never the layout; the readout states both days and the count inside over the total; "now" restores the whole forest; play grows the window from its start and holds it. Presentation only — no call, no write, never the address, never persisted, and the scale is `created`, never an indexed date. Acceptance F.170. Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
The v0.76 implementation. The graph view's timeline carries two native ranges over one drawn track, each a real slider for the keyboard and assistive technology; the thumb nearest the pointer is raised whole, so a click on the track moves the nearest end the way the single range did, and the choice is frozen while a thumb is held. The window is never empty except at the origin (start <= end - 1), which is what keeps both thumbs reachable at every position. The load-bearing line is a paint split, not a physics one: `on` stays the simulated set (filters + born), `shown = on && bornRank >= from` is what draw, hit-test, camera and heat read, and `step()` never sees `shown`. A leaf whose branch was planted a year before the window keeps the place the whole forest gave it, so a narrow window reads as a constellation in the shape of the forest. Play from a window replays from its own start and does not reseed — the reseed belongs to the origin. "Now" moves both ends, because a window that merely ends today is not the forest as it stands. Readout `2026-07-14 → 2026-08-21 · 770/1878` when a start is set, byte- identical to the v0.75 text otherwise; a day balloon over each thumb, one shared balloon when they are close. Never persisted (the quick search's rule), never in the address. Three locales, handbook sentence in three languages. F.170 is apps/studio/check-window.mjs, 30 static checks on F.137's boundary, run from tests/test_v076_window.py; 23 of them fail with the v0.75 view put back. Studio builds; suite green. Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
An agent operating a 1,877-node forest through MCP needed to keep a
screenshot. It planted a `type: media` node with a careful description
and an `origin` naming its own harness's file store, the plant succeeded,
`view` answered E_NOT_FOUND, its client rendered the envelope as "tool
call failed", and it filed a report whose first finding was that the
product has no way to store a binary. Every fact it needed had been true
since v0.48 — ingest(mode: "upload") takes {name, b64}, an image lands
as a media node with its bytes under _assets/, view serves exactly those
bytes — and not one of those facts was written where an agent reads. The
tool description said [{name, text}]. The skill taught text. plant
accepted a media passport that named no bytes at all. look had no way to
say whether bytes existed. And sniff(scope: "_meta") refused with "node
not found: _meta/_index", naming an id the caller never typed.
None of the report's fixes is taken: the upload channel exists, a fourth
view code would break J.3's oracle rule, and A.3 forbids dereferencing
origin for a reason. What is taken is the lesson under the report: a
surface whose every path exists and whose descriptions name none of them
is, to the model reading it, a surface with no paths.
Spec v0.77: J.1.2 rule 7 — a tool description names the neighbour that
does what it refuses, asserted off the served descriptions; C.7.5 — a
media passport planted through plant names bytes the forest holds,
refused E_SCHEMA at the write with a hint naming ingest and b64, the
Gardener exempt because archive: never references bytes at the source
by G.7's design; C.2.2 rule 6 — look carries payload_missing, or
payload_type with payload_bytes, for media, a stat and never an open;
C.6b — the bare _meta is E_SCHEMA naming the dialect and an unknown
scope is E_NOT_FOUND naming the scope the caller sent; J.5.12 — the
skill teaches bytes. view's envelope, origin, and J.1.2 rule 2 stay as
they are, each for a stated reason. Acceptance F.171.
Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
The v0.77 implementation, engine to handbook. Engine: plant of a type:media node without a payload — or with one that escapes the forest or names no file — is E_SCHEMA before the first write, rehearsed by dry_run and refusing a batch, with a hint that says where bytes go (`_media_payload_problems`); the Gardener's adopted= plant is exempt. look carries payload_missing for a media node without bytes and payload_type + payload_bytes when they are there, decided with a stat, and the flag survives a fields filter exactly as a dataset's does. _scope_where refuses the bare _meta as E_SCHEMA naming the dialect and an unknown scope as `scope not found: <what the caller sent>`, shared by sniff and calendar. view's envelope is unchanged to the byte. MCP: the instructions and the descriptions of ingest, view, plant, sniff and look each name the tool or parameter that does what they refuse — b64 and what an image becomes, look's flag before a view, "plant carries no bytes", what a scope is, what _meta/ is. The skill's saving block teaches b64 (check-skill F.171); the handbook's curl, the Integrations console's example and the tool table say the same in three languages. Tests that planted media passports pointing at nothing now plant real bytes and then delete or hand-edit them, which is how those states arise in the field. tests/test_v077_mcp_clarity.py walks the whole path once over MCP — ingest of a base64 PNG, look with type and size, view returning the same bytes, prune moving them to the graveyard — and reads the served tool list for J.1.2 rule 7. Suite 1666 green, 6 skipped. Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
v0.77 named the path bytes take into a forest, and the first agent to
walk it found the next gap: it had SEEN the screenshot it was uploading,
knew what the picture showed and which nodes it belonged beside, and the
only place that knowledge could go was a second call after the plant.
Between the two calls the node existed with a stub summary — a scent no
locate finds — and a batch of twenty screenshots was forty calls and
forty commits. A media node's worth is its bytes; its findability is its
passport, and the caller who holds the bytes very often holds the
passport too.
Spec v0.78: an upload entry MAY carry `passport: {title, summary, tags,
aliases, links, notes}` (new J.8.4). Shape-checked for every entry
before the first byte stages — a batch with one bad passport stages
nothing, the source_url rule applied to scent — and applied at curation
as an on_curate hook under exactly the rules a reviewed draft gets
(J.8.1): summary fitted to A.4, tags cleaned and capped, links
related-to only, to existing in-scope non-branch nodes, at most three,
confidence 0.3; aliases join the derived ones under the C.8 cap; notes
becomes the node's ## Notes. An entry with a passport is never sent to
the curation model; the rest of the batch keeps the bound curator, one
gate decided per draft. Not a second write path: the passport enters
where the approved draft already enters, so converter, content policy,
plant and commit are untouched.
Three things the review of the first cut added. Every refusal is
counted: a passport tag the rule refuses and an alias the cap clips are
counted in the report exactly as the model's are (G.4.2 rule 1). An
unapplied passport is named: bytes re-sent for a node that already
exists are refreshed and their passport is NOT applied, because a
refresh never curates, and the report MUST say so in
`passports_ignored`. And C.2.1 now carries a media node's ## Notes in
look, in the sweep's material and in the walk's entry — the first cut
claimed look carried it "everywhere" when it carried it for datasets
alone, and the path to a picture is look then view, the same shape as
look then query with the same gap. The describer still runs where a
vision role is bound: the body is its description, a passport carries
no body. The skill teaches the passport (J.5.12). Acceptance F.172.
Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
The v0.78 implementation, Station to handbook. Station: `validate_passport` checks the shape of every entry's passport once, before anything stages, and `stage_upload` hands the validated map back beside the provenance map. `compose.PassportGate` is the on_curate hook: a draft whose staged name carries a passport gets the reviewer's own rules — `_summary`, `Curator.clean_tags`, `_links` — and never meets the curation model; any other draft goes to the bound curator through the same object, whose `stats` merge the gate's own counters over the curator's so `tags_dropped` and `aliases_clipped` count a passport's refusals too. J.8.1's `approval_hook` threw the same count away and is fixed with it. The gate records what it pinned by staged name, and `_finish_ingest` writes `passports_ignored` off that set: a re-sent file is refreshed, its passport is not applied, and the caller is told by entry instead of finding the old scent on the next look. Engine: `look` carries `notes` for `type: media` under the dataset's budget and truncation mark; `harvest` and the walk's entry carry it for media as they do for datasets (C.2.1 rules 2 and 6). MCP: the ingest description names the passport, what it becomes, and that a malformed one is refused before any byte stages (J.1.2 rule 7). The skill's saving block shows the passport beside b64 and check-skill asserts it (F.172); the handbook's curl comment and the Integrations tool table mention it in three languages. tests/test_v078_upload_passport.py walks the path over MCP — a base64 image with a passport, look with the declared scent and the link, ## Notes in the body, payload_type and payload_bytes — then a mixed batch, six malformed passports that stage nothing, links dropped not fatal, the served description, counted refusals, a re-sent passport named, notes in look and in the sweep, and the review hook's own count. Suite 1674 green, 6 skipped. Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
Four spec versions since 0.74.0 (v0.75 through v0.78).
Behaviour changes worth reading before upgrading:
- The tag rule changed (v0.75). Accented tags and identifiers
(produção, BE-291, iso-27001, p95) are KEPT now. The old
^[a-z0-9][a-z0-9_-]*$ dropped every accented tag the Curator wrote,
silently, and bought nothing: nodes_fts folds diacritics on the way
in and on the way out, so a stored produção was always matched by
producao. One rule in models.py (Unicode categories, <=40 chars,
NFC+casefold dedup), curation cap 5 -> 12, every refusal counted as
tags_dropped. plant and graft refuse an invalid tag as
E_FRONTMATTER; a tag already on disk is grandfathered by exact
spelling, so an old forest still opens and an editor round-trip
still saves. The Curator also proposes aliases from the document
(union; a hand-written alias is never displaced). An already-
ingested forest gets the new scent through POST /v1/admin/recurate
{derive: ["scent"]}: a model-backed J.9 job that replaces the
summary, unions tags and aliases, and touches only source: ingest
nodes.
- A node MAY carry lang (BCP-47, v0.75). Absent is a real state; the
filter is exact (pt is not pt-BR), never a boost, counted by
coverage; a model is never its source. YAML reads `lang: no` as
False and the validator maps it back to Norwegian.
- Link proposals gain a human vote (v0.75): POST
/v1/forests/{f}/links/vote, write-gated. Accept sets confidence 1.0
and leaves the Ranger's managed population; reject deletes. There is
no MCP tool for it, by design: a model must not confirm its own
proposal. The default Ranger cold-prunes a cold 0.3 proposal, so
"stays at 0.3 forever" was never true.
- Through the wire, plant of a type: media node REQUIRES a payload
that resolves inside the forest and exists (v0.77, C.7.5), refused
as E_SCHEMA with a hint naming ingest(mode: "upload", files: [{name,
b64}]). A client that planted media passports pointing at nothing
must send the bytes first. The Gardener is exempt (archive: never
legitimately references bytes at the source). sniff(scope: "_meta")
is E_SCHEMA, an unknown scope is E_NOT_FOUND naming the scope sent,
and look on a media node carries payload_missing or payload_type +
payload_bytes. view's envelope is unchanged to the byte.
- An upload entry MAY carry passport: {title, summary, tags, aliases,
links, notes} (v0.78, J.8.4): shape-checked before the first byte
stages (one bad passport stages nothing for the whole batch),
applied at curation under the reviewed-draft rules, and an entry
with a passport is never sent to the curation model. Bytes re-sent
for an existing node are refreshed and their passport is NOT
applied; the report names them in passports_ignored. look, harvest
and the walk's entry now carry ## Notes for type: media.
- Every MCP tool description changed (v0.77, J.1.2 rule 7): each names
the neighbour that does what it refuses. The generated skill teaches
b64 and the passport; re-download it from Studio -> Skills.
- Studio: Explore's timeline is a window with a start and an end
(v0.76); prune and transplant from the console (J.5.17); tags
edited, applied in bulk and browsed (J.5.18); link proposals
accepted from the Links console (J.18).
Not measured yet: the v0.75 curator change against
bench/questions-locate-v1.json (F.169, baseline recall@1 0.711) needs
the local model. Implemented and stated, not measured.
Signed-off-by: Jimmy Wesley <rededown@hotmail.com>
This branch was previously deployed
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.
Release 0.78.0 — the forest keeps what it is handed
Four spec versions, v0.75 through v0.78, and the package moves 0.74.0 → 0.78.0.
Three of the four are one defect in different clothes: something the forest
was handed was dropped, and nothing said so. The fourth is a window.
None of those raised. Each was a filter, a description or a default doing
exactly what it was written to do, in a place where doing it quietly was the
defect.
What changed
The writer keeps what the reader was taught to keep (v0.75).
_clean_tagsvalidated against
^[a-z0-9][a-z0-9_-]*$, so every accented tag the Curatorwrote for a Portuguese forest was dropped on its way to disk — no error, no
count, no line in the report. An operator watching that sees a model that will
not write tags and goes to tune a model that was doing its job. The rule bought
nothing:
nodes_ftsisunicode61 remove_diacritics 2, so a storedproduçãoIS matched by
producao(measured). The same measurement cleared theidentifiers —
be-291,iso-27001,p95always passed the regex; only theprompt's "single words" and a cap of five stood between a document and its own
ticket number. v0.52 had already taught the reader to keep code-shaped tokens.
Two halves of one match, and only one had been measured.
Now one tag rule in
models.py(validate_tag/tag_key: Unicodecategories, ≤40, NFC+casefold dedup — the fold decides uniqueness only and
never respells), cap 12, every refusal counted (
tags_dropped), the promptasking for identifiers, names, categories and patterns. Enforced at
plant/graftwith exact-spelling grandfathering, so old forests still open.The Curator also proposes aliases from the document, guarded structurally:
an alias must occur in the body under the one exported
vine.fold, and theunion never displaces a hand-written one.
Three more gaps of the same shape — something the engine could already do that
nothing in the product asked it to:
lang(A.3.2): BCP-47, absent is a real state, a model is never a source, exact match (pt≠pt-BR), filter never a boost, counted bycoveragerecuratere-derived aliases with no model — incapable of producing one of the tags abovederive: ["scent"](J.13.6.1): the onederivemember allowed a model, a J.9 job, summary replaced, tags/aliases unioned,source: ingestnodes onlyAnd the operator gets the hands the agent already had: prune and transplant
(J.5.17 —
E_ANCHOREDrendered as navigable anchors,forcea seconddecision), tags edited and applied in bulk (J.5.18 — bulk unions and never
overwrites), link proposals accepted by group from the Links console.
The path was there and nothing named it (v0.77). An agent operating a
1,877-node forest through MCP needed to keep a screenshot. It planted a
type: medianode with anoriginnaming its own harness's file store, gotcreated: true, gotE_NOT_FOUNDfromview, watched its client render theenvelope as "tool call failed", and filed that the product cannot store a
binary.
ingest(mode: "upload", files: [{name, b64}])has done exactly thatsince v0.48 — and the tool description said
[{name, text}], the skill taughttext,
plantaccepted a media passport naming no bytes,lookcould not saywhether bytes existed, and
sniff(scope: "_meta")refused naming an id thecaller never typed. A surface whose every path exists and whose descriptions
name none of them is, to the model reading it, a surface with no paths.
None of the report's own fixes is taken (the upload channel exists; a fourth
viewcode breaks J.3's oracle; A.3 forbids dereferencingorigin). Taken:every tool description names the neighbour that does what it refuses (J.1.2
rule 7, asserted off the SERVED descriptions); wire
plantof media requires apayload that is contained and exists (C.7.5; the Gardener is exempt because
archive: neverlegitimately references bytes at the source);lookcarriespayload_missingorpayload_type+payload_bytesfor media — a stat, neveran open, the one place "will
viewwork?" is answerable before the call; abare
_metascope isE_SCHEMAand an unknown scope isE_NOT_FOUNDnamingwhat was sent.
view's envelope is deliberately unchanged: absent,out-of-scope and payload-less stay byte-identical.
The passport travels with the bytes (v0.78). The agent that followed
v0.77's path uploaded a screenshot it had already looked at, and the scent it
knew — what the picture shows, which nodes it belongs beside — had nowhere to go
until a second call and a second commit, with a window in between where the
node existed under a stub summary no
locatefinds. An upload entry may nowcarry
passport: {title, summary, tags, aliases, links, notes}: shape-checkedfor every entry BEFORE the first byte stages (a batch with one bad passport
stages nothing — the
source_urlrule), applied at curation under exactly thereviewed-draft rules of J.8.1,
notesbecoming the node's## Notes. An entrywith a passport is never sent to the curation model; the rest of the batch
keeps the bound curator. Not a second write path: converter, content policy,
plant and commit are untouched.
Two things the first cut got wrong and the review caught before commit, both
this release's own theme: a tag the rule refused was dropped uncounted (the
same flaw sat in J.8.1's
approval_hook, fixed with it), and bytes re-sent foran existing node were refreshed with their passport silently ignored — now
named in
passports_ignored, so the caller reaches forgraftinstead offinding the old scent on the next
look.look,harvestand the walk'sentry carry
## Notesfortype: mediaas they do for a dataset.The timeline had one end (v0.76). Explore's docked timeline was a
single-thumb scrubber — the forest as it stood on that day — and the question a
fed forest asks is what arrived BETWEEN two days. Now a start beside the end on
one
createdscale. The trap is the obvious filter: hide the outside nodes theway the replay hides the unborn (out of the physics too) and the survivors —
leaves whose branch is a year older — have no spring left and fold into the
centre, so the picture answers "what" and lies about "where". The window is a
PAINT split, never a physics one: a node outside it leaves the picture and
never the layout. Never persisted, never in the address.
Measured
produçãostored,producaoqueried, throughnodes_fts(remove_diacritics 2)be-291/iso-27001/p95through_TAG_REand the tokenizerbench/questions-locate-v1.json(baseline recall@1 0.711)Known gaps, stated rather than discovered
its effect on the labelled set has not been measured.
unsupported. The v0.77 report's"send any file type" is a Part G round of its own: a PDF converter and a
fallback for formats no converter claims.
langreaches a passport from caller frontmatter only; the docx/HTML/configprecedence of A.3.2 rule 3 is not wired.
isErroron every refusal) stays. The client that dropped theenvelope reads the flag alone, the client rule 2 was written for reads the
body alone, and a Station cannot write for both at once.
For existing callers
plant/graftcarrying a tag the new rule refuses isE_FRONTMATTER; tags already on disk are grandfathered by exact spelling.plantoftype: mediawithout an existing,contained
payloadisE_SCHEMA. Send the bytes throughingestfirst.sniff(scope: "_meta")isE_SCHEMA; an unknown scope isE_NOT_FOUNDwith a message naming the scope, distinct from a missing node's.
ScopedVineenumerates its parameters — a new engine kwarg (lang)needed its own lines in
policy.pyandmcp_surface.py, which cost a live400 during the round.
b64and thepassport now, and
forests()tells an agent when the one it holds is stale.Acceptance
F.162 – F.172. Full suite green locally (1675 passed, 6 skipped, Python 3.12) and running in CI on this PR;
python -m build+twine checkpassed (wheel 205 kB, sdist 506 kB).