Skip to content

fix: keep a Mate's pictures for a week; env values stored and reported without echo - #36

Merged
fxck merged 13 commits into
mainfrom
p43/shots
Oct 5, 2026
Merged

fxck merged 13 commits into
mainfrom
p43/shots

Conversation

@fxck

@fxck fxck commented Oct 5, 2026

Copy link
Copy Markdown
Member

Two defects from a long Mate run, and the env defects a review of their fix found.

Pictures

  • A Mate's pictures are kept for up to 7 days instead of only the newest twenty. Past 256 MiB on disk the oldest go first; the newest picture and every picture a saved description names are always kept. In a 7-hour run the Mate took 69 pictures, and its description of 30 was refused three times.
  • A picture already on a change keeps its HQ address after its file is pruned, so describing the same change again after a push still shows it.
  • A picture gone while a description is saved is refused in plain words, never reported as kept. A refusal lists what is kept.
  • The prune skips entries whose file is gone, and does nothing when the pairs' records can't be read.
  • zerops_browser says keptFor: "up to 7 days".

Env values

  • zerops_env set stores plain values byte for byte. Before, every value went through the preprocessor, so Xy<9z was stored as Xy. Only a value with <@ is expanded now, each on its own.
  • No error, suggestion, diagnostic or warning repeats a value: a malformed entry is named by its place, a broken expression by its key, a ref chain too deep by its reference, and a shadow warning names the key and both places.

Tests for each case fail without its fix. go test passes for workflow, tools, ops, preprocess and integration; go vet is clean; golangci-lint reports no issues on the touched packages.

🤖 Generated with Claude Code

fxck added 13 commits October 6, 2026 00:23
Run 12's Sage described a 3.5-hour redesign with 30 pictures taken over
seven hours (shot-4 to shot-69); the newest-twenty rule had pruned every
"before" and, on each retry, the shots taken minutes earlier, so
describe-change refused three times and Sage rebuilt version 1 of the
site to photograph it again. One store, numbering gap-free across the
conversation and its helpers: the rule itself was the cause.

A picture is now kept for PictureKeepFor (7 days) after it is taken,
while the store holds at most PictureStoreBytes (256 MiB), past which
the oldest go first; the newest is always kept, and every picture a kept
description names outlives both. A refusal names the pictures this Mate
keeps and how long one is kept, and zerops_browser's result says how
long its picture is kept (screenshot.keptFor).
An entry without "=" is often a secret pasted alone; the refusal
repeated it whole into an error the agent and the UI show.
…eeps what it cannot judge

A successful describe forgets its words, so the pictures they showed
fell back to the age and size bounds; once pruned, describing the same
change again after a push was refused although HQ still served them.
A pruned picture that a change carries now keeps its entry and its
Uploads, found without its bytes, and only another change needs the file.

An entry whose file is gone (a process that died between the remove and
the index write) is neither read, listed nor counted, and the next prune
drops it unless a change carries it. The byte bound counts files on
disk, not recorded sizes. When a pair's record cannot be read, nothing
is pruned: which pictures a kept description names is unknown then.
The describe checked its pictures before reading the Mate's state; a
helper's screenshot could prune one before the words went on. That
failure was not errCannotAttach, so the Mate was told the words were
kept and would go on with the next delivery, which then failed silently
every time while the stuck words protected their other pictures.

changeBody now reads every picture for the change it goes onto before
attaching any, after the words are kept, and names those gone in their
own error: the describe refuses, naming the picture and what is kept,
and drops the words; a later delivery meeting one drops them with a line
that says so. keptFor says "up to 7 days", since the byte bound can come
first, and one older picture is "1 older picture".
…he input

wrapParseError quoted the first 80 characters of the input, and a batch's
input is every entry joined, so one bad expression echoed other entries'
values, secrets included, into an error the agent and the UI show.
Every value went through one zParser batch, though only <@…> is an
expression: DB_PASSWORD=Xy<9z was stored as "Xy", <html> as "html", and
with two entries the batch delimiter was swallowed and the call failed.
Only a value holding <@ is expanded now, each on its own, so one entry
can never change another; a failure names the entry by its key with
zParser's reason. zerops_import's project envVariables take the same
path. setVar/getVar across entries stays zerops_preprocess's batch.
The depth error quoted the value being expanded, which by then holds the
values already resolved into it. The bound is checked where the next
reference is followed, and the error names that reference (host.var).
… value

zerops_env set's shadow warnings printed the winning yaml or service
value, masked only for credentials zcp owns, while zerops_env get
returns keys, not values. The warning now names the key, the project,
and the yaml or service that wins.
zParser v2.1.2 panics ("index out of range") on a call whose argument
follows its comma directly, <@generateRandomInt(<10>,<50>)>, and nothing
recovered it: one value took the whole zcp process, and the agent's MCP
server, down. expand now answers a panic as an error that says to write
a space after each comma, never the input. The form is not rewritten:
the platform's import runs the same zParser, so a value zcp accepted
could still fail there.

core.md and the zerops_preprocess description taught the no-space form;
both now write "<@f(<a>, <b>)>" and say why.
5733c6b expanded each <@…> value on its own, which broke what spans
entries: a key pair's other half (<@getvar(jwtPrivate)> after
<@generateRSA2048Key(<jwt>)>) and a variable set by one entry and read by
the next failed with "variable not found", in zerops_env set and in
zerops_import's project envVariables. The values holding <@ expand in
one batch again, in the order given; every other value is still stored
byte for byte. A failing batch is named by the key of the first entry it
fails at (the shortest failing prefix), never by a value.

A value without <@ is stored exactly as written, with no unescaping:
core.md's escaping advice now names where it applies (an import YAML
with the preprocessor on, and a <@…> value), and the zerops_env
description says other values stay verbatim and modifiers work only
inside <@…>.
…bad record never stops the prune

An address-only entry (a picture whose file was pruned, kept for the
change it is on) never expired. It is now kept only while that change is
a pair's current one, and for PictureAddressKeepFor (90 days) at most.

KeptPictures takes the change being described and lists only what that
change can show: a picture on disk, or one whose address is that
change's. Another change's address no longer passes for kept.

One unparseable pair record made ListServiceMetas fail and turned the
prune off for good, silently. Unparseable records are skipped now; only
a pairs' directory that cannot be read stops the prune.

Reading the store wrote the index back on every read, so a full disk
made a kept picture read as gone; reads now write nothing.
…es only what can happen

The quick check took any kept entry, so a picture kept only as another
change's address passed it and was then refused as "no longer kept",
while the refusal listed it as kept. Pictures are now checked after the
pair is known, for its current change, and the refusal lists only what
that change can show.

Any error reading the store counted as a picture gone, so an unreadable
store dropped words the old code kept. Only ErrPictureNotKept is gone
now; any other error is answered as itself, and kept words stay kept.

After errCannotAttach the Mate was told its words go onto #N with the
next delivery, which HQ's own refusal made impossible. A refusal HQ will
repeat (a 4xx other than 408/429, or a picture over 20 MiB) now drops
the words and says why; HQ refusing for now keeps them, as when it does
not answer. The delivery line's note is a whole sentence.
ListServiceMetas and the picture prune's readablePairs both scanned the
services directory for records; serviceMetaFiles is that scan now, and
each reads the records by its own rule (all or nothing, or skip a bad
one).
@fxck
fxck merged commit d98e7b3 into main Oct 5, 2026
3 checks passed
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