docs(architecture): a refusal can BE the positive signal, and creation is a second limit - #4790
Conversation
…n is a second limit Two shapes measured today that ReadingCiSignals did not carry, both in the family it already documents (an answer that reads like a pass) and both caught only because someone looked past the signal at the consequence. A watcher polled a file's sha with `--jq '.sha' || true` and compared it to a baseline. Under a secondary rate limit the jq yielded the 403 BODY, `!=` against the 40-hex baseline was true, and it printed "ACT NOW … MOVED" with a remedy. Nothing had moved. This is the inverted twin of the watcher that reports "not yet" forever, and strictly worse: it manufactures an action instead of withholding one. Two correct decisions compose into it — `|| true` so one refusal cannot kill a long watch, and `!=` as the change test — and together they mean any failed read is a positive result. The guard is to validate the value's SHAPE before comparing, make "could not read" a third printed outcome, and dry-run the watcher against a forced failure. Separately, the page's primary/secondary table concerns reads, and there is a SECOND secondary limit on content creation that is reached independently. `gh issue create` exited 0 with both streams empty and created nothing; the same request over REST named the 403. The consequence that matters is a lost review-thread reply, which leaves the gate red with no signal, so every creation is read back — keyed on something only the NEW content has, because an existence check passes on a stub and on any earlier session's reply. Verified: MeshWeaver.Documentation.Test 601 succeeded, 0 failed, 0 skipped; the project builds Release -warnaserror with 0 Warning(s), 0 Error(s). No internal links added, so nothing new for DocumentationLinkIntegrityTest to resolve. No What's New entry: measured over the last 25 commits touching Data/Architecture, none of the doc-only ones carries one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Address the unresolved verification, workflow remediation, and What's New documentation findings before approval.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
This documentation-only PR adds guidance on false change signals from invalid reads and silent content-creation failures under rate limits.
Changes:
- Documents refusal polarity and input validation.
- Covers independent creation limits and REST-based verification.
- Explains read-back checks for newly created content.
| File | Summary |
|---|---|
src/MeshWeaver.Documentation/Data/Architecture/ReadingCiSignals.md |
Adds CI signal and GitHub API failure guidance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| **Verify every creation by reading it back, keyed on something only the NEW content has.** An | ||
| existence check such as `select(.in_reply_to_id == <ID>)` passes on a pre-existing reply and on a stub, | ||
| and a retrier keyed that way reported success while a wrong reply sat there untouched. Byte-compare the | ||
| fetched body against the file that was posted, or at minimum assert its exact length. An existence | ||
| check is sound only against a baseline measured *before* the write. |
There was a problem hiding this comment.
Valid, and it is the sharper form of the very point the section is making. Length is not an identity proof: a stub, or an earlier session's reply, can match a length as easily as it can match an in_reply_to_id, so "at minimum assert its exact length" offers a weak check as if it were a sufficient one. Taking it.
Two separate questions were collapsed in that sentence, and the fix separates them:
- Did THIS write create a comment? The answer is the
idin the write's own REST response, re-fetched. Nothing derived from the body can answer it. - Does that comment carry the content intended? A byte-compare of the fetched body against the file posted.
Length answers neither, so it is going out rather than being demoted. The revision states the pair, and keeps the existing point that an existence predicate is sound only against a baseline measured before the write — which is the case where no response id is available, for instance when auditing somebody else's earlier posts.
Worth recording that the scripts behind this section already did the strong thing and the prose did not describe it: they took number/id from the POST response, re-fetched by that id, and only then compared a length. So the guidance was weaker than the practice, which is the more common direction for this kind of drift and the reason it is worth fixing rather than arguing.
Fixed in the next push.
… questions Copilot's finding is right and it is the sharper form of the point the section was making. The text offered "byte-compare the fetched body … or at minimum assert its exact length", which presents a weak check as a sufficient one: a stub, or an earlier session's reply, matches a length as easily as it matches an `in_reply_to_id`. Two questions were collapsed into one sentence, and they have different answers. Whether THIS write created a comment is answered only by the `id` in the write's own REST response, re-fetched by that id; nothing derived from the body can answer it. Whether that comment carries the intended content is answered by a byte-compare against the file posted. Length answers neither, so it is removed rather than demoted. The existing point about a pre-write baseline is kept and scoped to the case where no response id exists — auditing somebody else's earlier posts. The scripts behind this section already did the strong thing while the prose described the weak one: they took `number`/`id` from the POST response and re-fetched by it. The guidance was weaker than the practice, which is the easier direction for this drift to run. Demonstrated rather than asserted: the reply to this finding was posted, re-fetched by the id the write returned, and byte-compared against its source file before this push. Verified: MeshWeaver.Documentation.Test 601 succeeded, 0 failed, 0 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

What this adds
Two failure shapes measured on 2026-09-19 that
ReadingCiSignals.mddid not carry. Both belong to the family the page already documents, an answer that reads like a pass, and both were caught only by looking past the signal at the consequence.A comparison against an unvalidated read turns a refusal into "ACT NOW". A watcher polled a file's sha with
--jq '.sha' || trueand compared it to a baseline. Under a secondary rate limit the jq yielded the 403 body,!=against the 40-hex baseline was therefore true, and the watcher announced that the file had moved and named a remedy. Nothing had moved. It is the inverted twin of the watcher that reports "not yet" forever, and worse, because it manufactures an action rather than withholding one. Two individually-correct decisions compose into it:|| trueso one transient refusal cannot kill a long watch, and!=as the change test.The generalisation, which an agent stated more sharply than I did: the polarity decides whether a failed read waits or fires. Waiting for the value you expect fails safe. Waiting for difference from a value you remember fails dangerous. Same information, opposite safety.
Content creation is a second secondary limit, and the
ghporcelain exits 0 under it.gh issue createreturned exit 0 with both streams empty and created nothing; the same request over REST named the 403 immediately. The consequence that matters is a silently lost review-thread reply, which leaves the review gate red with no signal that anything went wrong. The page's existing primary/secondary table covers reads only. Measured today, the two limits are not ordered: creation was refused at 08:51Z while reads still worked, and reads were refused at 09:11Z withcore: 5000/5000 remaining.The section also records that a read-back must key on something only the new content has.
select(.in_reply_to_id == <ID>)passes on a stub and on any earlier session's reply, and a retrier keyed that way reported success while a wrong reply sat there untouched.Verification
MeshWeaver.Documentation.Test: 601 succeeded, 0 failed, 0 skipped.dotnet build -c Release -warnaserroron that project: 0 Warning(s), 0 Error(s).DocumentationLinkIntegrityTestto resolve.Data/Architecture, none of the doc-only ones carries one.ReviewFindingsAnswered.mdand shares no file with this.Documentation only. No behaviour changes, so no address needs recycling.
🤖 Generated with Claude Code