Skip to content

docs(architecture): a refusal can BE the positive signal, and creation is a second limit - #4790

Merged
rbuergi merged 2 commits into
mainfrom
docs/reading-ci-signals-limit-shapes
Sep 19, 2026
Merged

rbuergi merged 2 commits into
mainfrom
docs/reading-ci-signals-limit-shapes

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

What this adds

Two failure shapes measured on 2026-09-19 that ReadingCiSignals.md did 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' || true and 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: || true so 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 gh porcelain exits 0 under it. gh issue create returned 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 with core: 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 -warnaserror on that project: 0 Warning(s), 0 Error(s).
  • No internal links added, so nothing new for DocumentationLinkIntegrityTest to resolve.
  • No What's New entry, measured rather than assumed: over the last 25 commits touching Data/Architecture, none of the doc-only ones carries one.
  • Distinct from docs: a control can be the thing that cannot fail, and a read-back needs a baseline #4788, which edits ReviewFindingsAnswered.md and shares no file with this.

Documentation only. No behaviour changes, so no address needs recycling.

🤖 Generated with Claude Code

…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>
Copilot AI lite review requested due to automatic review settings September 19, 2026 09:39
@rbuergi
rbuergi enabled auto-merge September 19, 2026 09:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity

Open (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.

Comment on lines +357 to +361
**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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 id in 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.

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

443 tests  +5   443 ✅ +5   55s ⏱️ -3s
  3 suites ±0     0 💤 ±0 
  3 files   ±0     0 ❌ ±0 

Results for commit 274105d. ± Comparison against base commit 1da61b3.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

265 tests  ±0   265 ✅ ±0   2m 18s ⏱️ -2s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 274105d. ± Comparison against base commit 1da61b3.

♻️ This comment has been updated with latest results.

… 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>
@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 553 tests  ±0   1 553 ✅ ±0   3m 9s ⏱️ -3s
    2 suites ±0       0 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 274105d. ± Comparison against base commit 1da61b3.

♻️ This comment has been updated with latest results.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

    3 files  ±0      3 suites  ±0   5m 31s ⏱️ -12s
1 857 tests ±0  1 857 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 858 runs  ±0  1 858 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 274105d. ± Comparison against base commit 1da61b3.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

710 tests  ±0   518 ✅ ±0   5m 34s ⏱️ ±0s
  3 suites ±0   192 💤 ±0 
  3 files   ±0     0 ❌ ±0 

Results for commit 274105d. ± Comparison against base commit 1da61b3.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    5 files  ±0      5 suites  ±0   12m 20s ⏱️ +27s
3 679 tests ±0  3 677 ✅ ±0  2 💤 ±0  0 ❌ ±0 
3 683 runs  ±0  3 681 ✅ ±0  2 💤 ±0  0 ❌ ±0 

Results for commit 274105d. ± Comparison against base commit 1da61b3.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   17 files  ±0     17 suites  ±0   29m 49s ⏱️ +7s
8 507 tests +5  8 313 ✅ +5  194 💤 ±0  0 ❌ ±0 
8 512 runs  +5  8 318 ✅ +5  194 💤 ±0  0 ❌ ±0 

Results for commit 274105d. ± Comparison against base commit 1da61b3.

@rbuergi
rbuergi added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit 4859a78 Sep 19, 2026
37 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.

2 participants