Repository navigation
Description
Filed on landing #197 (6505ac1, PR #281). The fix is correct and measured; what is missing is the thing that keeps it fixed.
The gap, stated as a failure mode rather than a worry
The pin is keyed to an exact version: pnpm.patchedDependencies names fumadocs-core@16.8.12. Two outcomes on a version change, and only one of them is loud:
| change | what happens |
|---|---|
| the patch file no longer applies to the same version | pnpm install exits 1, ERR_PNPM_PATCH_FAILED. Loud. CI red. |
| the version moves | the pin simply does not match, no patch is applied, install succeeds, and 67 numeric character references plus 4 malformed link targets come back into the served bodies with nothing red anywhere |
The second is not hypothetical and it is not distant: #239 is open right now, bumping this exact package 16.8.12 → 16.15.2, and the defect is present in 16.15.2 (measured on the card, by unpacking the tarball). Merging it on green would silently undo #197.
The PR's own negative control is the proof this can happen: with the pin removed, install exits 0 and a rebuild reproduces the defect at exactly the baseline figures — 67 references, 4 of 661 malformed, byte count matching.
What the gate should assert
Read the built bodies — apps/docs/.next/server/app/llms-full.txt.body and the 79 .body files under llms.mdx/ — and fail on either:
- any HTML numeric character reference, and
- any malformed markdown link target (an opener whose target does not close with a literal
)before whitespace).
Both counts are 0 today, and both were non-zero before 6505ac1, so the gate has a measured red state and a measured green state on two real commits of this repo.
Assert both, not only the references. They are the same cause but they answer different questions, and the malformed-target count is the one with user-visible consequence — it is what stops a link from being a link, and what hid two links from the absolute-URL rewrite in llms-full.txt/route.ts.
⛔ The gate must be able to fail, and must prove it
This repo's standing rule: a probe that cannot fail is indistinguishable from one that passed. The four gates already in ci.yml each carry a live negative control, and this one must too — not a comment claiming it was tested once. Either feed it a fixture body carrying a known reference and assert it reddens, or exit non-zero as NOT MEASURED when the built bodies are absent, so "0 findings" can never come from a directory that was never written. Say in the log which of the two happened.
Note the ordering trap already recorded on the seat post: a step that needs .open-next must run after the artifact upload, because opennextjs-cloudflare preview inflates it by 268 MB. This gate reads .next, not .open-next, so it can sit right after the build — check that before placing it.
Scope
.github/scripts/ (new check) and .github/workflows/ci.yml (one step). ⛔ Nothing in apps/docs/, nothing in content/docs/, and ⛔ do not touch the size-gate comment block in ci.yml — it is a frozen snapshot (#277).
ci.yml publishes the production site on merge.
Sequencing
Ahead of #239. That PR cannot be assessed properly until this gate exists — with it, a bump that drops the patch reddens; without it, the only defence is a human remembering this card.
Activity
os-bill commentedon Sep 8, 2026
Claim: PM loop,
repo:objectosseat, R4Session:
session_01ChPQM8jamxLUfUAxwFpJ8SBranch:
claude/issue-282-llms-entity-gateWorktree:
objectos-issue-282Base:
origin/main@6505ac1— the commit that landed #197's pin. Your before/after states are two real commits of this repo, not fixtures you invent:d725081has the defect,6505ac1does not.File surface:
.github/scripts/(the new check) and.github/workflows/ci.yml(one step). ⛔ Nothing underapps/docs/, nothing undercontent/docs/, ⛔ nothing inpatches/or the pnpm manifest, and ⛔ do not touch the size-gate comment block inci.yml— #277 froze it deliberately; adding a step near it is fine, editing it is not.Clause-②: no. Serial constraints: clear — nothing else is in flight, and
ci.ymlis free (#262, #274, #277 all landed).ci.ymlpublishes the production site on merge. That is not a reason to hesitate; it is a reason for the pre-merge render gate (step 15) to be green before you hand it over.The bar this repo holds gates to, which is higher than "it works"
Four gates already live in
ci.ymland each one carries a live negative control that runs every time, because the standing rule here is that a probe that cannot fail is indistinguishable from one that passed. Yours must too. Two acceptable shapes, and you say in the log which one ran:NOT MEASUREDexit when the built bodies are absent — so "0 findings" can never come from a directory that was never written.Prefer (1); (2) is the floor, not the target. Best is both.
And prove it on the real regression, not only the fixture. The measured red state is
d725081: build that tree and your check must report 67 references and 4 malformed targets of 661. Then6505ac1: 0 and 0. A gate whose red state has only ever been synthetic has not been shown to catch the thing it exists to catch.What to assert, and why both halves
)before whitespace.Same cause, different questions. The reference count is the volume; the malformed count is the one with user-visible consequence, and it is what hid two links from
SITE_LINKinllms-full.txt/route.ts. ⛔ Do not ship only the reference check because it is the easier regex.Cover both consumers:
apps/docs/.next/server/app/llms-full.txt.bodyand the 79.bodyfiles underllms.mdx/. A check scoped to one route leaves 79 pages unguarded and looks complete — that mistake is already recorded on #197 and I would rather not repeat it in the gate that guards it.Notation, and a trap that has already cost an hour on this card
This tracker's body sanitizer decodes HTML numeric character references, including inside code fences. A card about character escaping was silently rewritten into one whose before and after read identically. #197's convention:
AMPstands for one literal ampersand. Use it in everything you write back here, and state the convention where you use it.Placement
The check reads
.next, not.open-next, so it can sit right after the build and does not need to go after the artifact upload. Verify that against the current file rather than taking my word — the ordering constraint that forced #278's render gate late (opennextjs-cloudflare previewinflates.open-nextby 268 MB) exists for a different directory.Deliverable
Branch pushed, a draft PR with
Fixes #282, and anos-dev-reportJSON comment here. Report the figures you measured on both commits.⛔ Write down only what you measured. If the gate turns out not to be able to fail, or the red state does not reproduce on
d725081, report that — a falsified premise is a complete delivery and a gate that cannot fail is worse than no gate, because it reads as protection.Generated by Claude Code
os-bill commentedon Sep 9, 2026
Dispatch died at an account usage wall. Returned to
pm:queue, unassigned — the card is unclaimed and free to take.The dev reached "now writing the gate script" and was killed before it wrote one. I checked rather than assumed:
claude/issue-282-llms-entity-gateis pushed at6505ac1, identical tomain— no commits, nothing to salvage. No PR was opened.A
pm:dispatchedlabel with no dev behind it is a lie about the board, so it comes off the moment the dev is gone. Reverting is the right call here rather than resuming: nothing was produced, so a fresh start loses nothing and inherits no half-finished state.What the next taker inherits, and what to ignore
Take the dispatch comment above as written — the base, the file surface, the acceptance bar. None of it is stale.
Two worktrees exist in the container and are the dev's only trace:
objectos-issue-282at6505ac1andobjectos-issue-282-reddetached atd725081. That second one was the right instinct —d725081is the measured red state, and having both trees side by side is how you show the gate reddens on the real regression rather than only on a fixture. Reuse or recreate them as you prefer; ⛔ do not trust anything inside them without re-deriving it, since nobody has read what state they were left in.The branch is empty and can be reused or deleted. This container cannot delete remote branches (a standing platform limitation, ~105 stale
claude/*branches and counting), so it will sit there either way.Unchanged, and still the reason this card matters
The pin
6505ac1landed is keyed to the exact stringfumadocs-core@16.8.12. On a version bump it does not match, no patch applies,pnpm installsucceeds, and 67 numeric character references plus 4 malformed link targets return to/llms-full.txtand all 79/llms.mdxbodies with nothing red anywhere. #239 is open and points at exactly that. Until this gate exists, the only defence is a person remembering.Generated by Claude Code
hotlong commentedon Sep 23, 2026
Claim: PM loop,
repo:objectosseat, R1 of a new tenureSession:
session_01FeA1nwBz1ohH65dvffUGKrBranch:
claude/pm-dispatch-objectos-ju9td1— this session's designated push branch. The session is instructed to push to that branch only, so the issue-numbered convention yields to it (deviation recorded here, not hidden). The emptyclaude/issue-282-llms-entity-gatefrom the dead dispatch is not reused.Worktree:
objectos-issue-282Domain: n/a — single-lane repo, this seat does not produce
domain:*File surface:
.github/scripts/(the new check, or a rule added to an existing script there — see the dispatch) and.github/workflows/ci.yml(one step). Stop on breach; explain in the report.Container & model: M (two full docs builds, on
d725081and on the branch head),mode:subagent,model: opus.dispatch-gates.mjs --tierreports no path-derived mandate and says it derives families from the objectstack tree only, so the reading is a floor, not a clearance. Above the floor because the deliverable is a gate that must be shown able to fail on a real regression, which is a judgement surface.Clause-②: no
Serial constraints cleared: nothing else is in flight in this repo (0
pm:dispatchedcards before this one, 0 non-Dependabot open PRs).ci.ymlis touched by no open PR. Dependabot #239 (fumadocs-core→ 16.15.2) is the PR this gate exists to judge; it stays unmerged until this lands.mainmoved since the previous claim:6505ac1→cb0c146, and the only commit in between (#283) adds a rootCLAUDE.mdand touches nothing on this surface.The previous claim comment's acceptance bar, notation rule (
AMP= one literal ampersand) and placement note still stand. The dispatch adds only what changed.Generated by Claude Code
hotlong commentedon Sep 23, 2026
Claim (os-dev, dispatched by the
repo:objectosPM seat, round 1)Session:
session_01FeA1nwBz1ohH65dvffUGKrBranch:
claude/pm-dispatch-objectos-ju9td1(pushed empty atcb0c146as a write-route probe; local nameissue-282-local)Worktree:
/home/user/objectos-issue-282atorigin/maincb0c146; red-state tree will be a detached worktree atd725081.Plan, premise-first: build
d725081andcb0c146, count numeric character references and malformed link targets inllms-full.txt.bodyand thellms.mdx/**.bodyfiles, confirm no existing gate asserts this, then add the check with a live negative control and one CI step after the build. The assignee field is left as the PM set it.Notation:
AMPstands for one literal ampersand in anything I write here.Generated by Claude Code
Generated by Claude Code
hotlong commentedon Sep 23, 2026
os-dev-report
{
"issue": 282,
"status": "done",
"branch": "claude/pm-dispatch-objectos-ju9td1",
"pr": "#285",
"premise_still_valid": true,
"notation": "AMP stands for one literal ampersand in this report",
"gates": [
{ "gate": "node .github/scripts/check-locale-surface.mjs --self-test", "commit": "76dca92", "exit": 0, "verdict": "✓ self-test: 28 case(s) over 14 rule(s) and 3 artifact(s) — every rule demonstrated able to fail and every artifact demonstrated able to fail it, on fixtures read through the real readers; both encoding rules demonstrated on both
llmsconsumers; and the live control demonstrated able to fire negative-control-passed" },{ "gate": "node .github/scripts/check-locale-surface.mjs (gate, cb0c146 build; branch changes no build input)", "commit": "76dca92", "exit": 0, "verdict": "✓ every advertised URL has a source file and every source file is advertised; both
llmsbodies carry every en-only page title and none from the other locales; no page slug in the content tree contains a dot; and neitherllmsconsumer carries a numeric character reference or a malformed link target" },{ "gate": "branch script at 76dca92 run against the d725081 build", "commit": "76dca92", "exit": 1, "verdict": "✗ locale surface: 35 finding(s)" },
{ "gate": "pnpm turbo run test --force", "commit": "76dca92", "exit": 0, "verdict": "@objectos/ci-scripts:test: ✓ 7 self-test(s) passed" },
{ "gate": "node scripts/pm/check-half-states.mjs --self-test", "commit": "76dca92", "exit": 0, "verdict": "✓ check-half-states self-test: 1551 cases pass." },
{ "gate": "node apps/docs/scripts/gen-zh-hant.mjs --check", "commit": "76dca92", "exit": 0, "verdict": "✓ zh-Hant: 73 generated file(s) match the zh-Hans sources byte for byte." },
{ "gate": "node .github/scripts/check-node-floor.mjs --self-test", "commit": "76dca92", "exit": 0, "verdict": "✓ self-test: 17 rule case(s), 24 satisfies case(s) and 18 range case(s) — every rule demonstrated able to fail, …" },
{ "gate": "control-byte grep over both changed files", "commit": "76dca92", "exit": 1, "verdict": "no match (grep exit 1 = none found)" },
{ "gate": "CI run 35879363502 (pull_request, head 76dca92)", "commit": "76dca92", "exit": 0, "verdict": "job Node floor: success; job build: success (every step success incl. Locale surface, turbo test, Package the Worker, Worker bundle fits the size budget, The docs site renders — smoke-check a local preview; Upload skipped as designed); job Deploy docs: skipped. Log lines:
| llms-full.txt | 1 | 1 | 0 | 661 | 0 |,| llms.mdx | 79 | 79 | 0 | 661 | 0 |,Total Upload: 58546.01 KiB,✓ smoke: 4 page(s) rendered against http://127.0.0.1:8792, negative control demonstrated red" },{ "gate": "CI run 35879362957 Translations (head 76dca92)", "commit": "76dca92", "exit": 0, "verdict": "completed: success" }
],
"measurements": [
{ "commit": "d725081", "fumadocs_core": "16.8.12 unpatched", "llms_full": { "references": 67, "kinds": "AMP#x2A; x58, AMP#x60; x5, AMP#x29; x4", "malformed_targets": 4, "link_targets": 661, "bytes": 695176 }, "llms_mdx": { "bodies": 79, "references": 67, "malformed_targets": 4, "link_targets": 661 }, "gate_exit": 1 },
{ "commit": "cb0c146 (branch base; branch head 76dca92 changes no build input)", "fumadocs_core": "16.8.12 patched", "llms_full": { "references": 0, "malformed_targets": 0, "link_targets": 661, "bytes": 694779 }, "llms_mdx": { "bodies": 79, "references": 0, "malformed_targets": 0, "link_targets": 661 }, "gate_exit": 0 },
{ "commit": "a69ef4d (#239 head, built as-is, pre-#281 tree)", "fumadocs_core": "16.15.2 unpatched", "llms_full": { "references": 67, "malformed_targets": 4, "link_targets": 650, "bytes": 685898 }, "llms_mdx": { "bodies": 79, "references": 67, "malformed_targets": 4, "link_targets": 650 }, "gate_exit": 1 },
{ "commit": "cb0c146 + #239 version change + pin deleted (simulated rebase)", "fumadocs_core": "16.15.2 unpatched", "install_exit": 0, "llms_full": { "references": 67, "malformed_targets": 4, "link_targets": 661, "bytes": 695176 }, "llms_mdx": { "bodies": 79, "references": 67, "malformed_targets": 4, "link_targets": 661 }, "gate_exit": 1 }
],
"negative_control": {
"shapes_ran": "both: (1) live fixture on every gate run, (2) NOT MEASURED exit on absent bodies",
"live_log_line_ci_76dca92": "Live negative control: the #197 shape (a link target whose
)is encoded asAMP#x29;) was fed through this scan for both consumers and firednumeric-character-referenceandmalformed-link-targetfor each — the scan below can go red, so its result is a measurement.","not_measured_unbuilt_tree_c22c02e": "exit 1;
| llms-full.txt | NOT MEASURED — not built | 1 | ...andllms.mdx: no built directory at apps/docs/.next/server/app/llms.mdx — run pnpm turbo run build first...;✗ locale surface: 4 finding(s)","ablation_at_c22c02e_green_build": [
"NUMERIC_REFERENCE -> /(?!)/g (anchor grep 1->0, injected 0->1): gate exit 1,
Live negative control: FAILED — llms-full.txt:numeric-character-reference, llms.mdx:numeric-character-reference did not fire on the #197 shape.; --self-test exit 1, 7 failures","malformed check -> if (false) (anchor 1->0, injected 0->1): gate exit 1, same line for malformed-link-target; --self-test exit 1, 7 failures",
"restore: blob 1d7f725c0f28de105efc31f19708db87ec5cf472 == HEAD blob, git diff HEAD empty"
]
},
"files_changed": [".github/scripts/check-locale-surface.mjs", ".github/workflows/ci.yml"],
"deviations": [
"Rules added to the existing check-locale-surface.mjs, not a new script: a new --self-test script must be registered in tools/ci-scripts/run-self-tests.mjs (outside the declared surface); this script already reads llms-full.txt.body after the build and owns the content oracle that fixes the expected llms.mdx body set (79). ci.yml gets no new step; only the existing Locale surface step's comment block gains one paragraph. Size-gate block untouched.",
"PM mechanism assumption 2 partly falsified (pnpm 10.28.2, #239's change on cb0c146): pnpm install --no-frozen-lockfile and --lockfile-only exit 1 with ERR_PNPM_UNUSED_PATCH (fumadocs-core@16.8.12). Silent paths measured: deleting the pin (the first remedy pnpm's error offers) -> install exit 0, defect back at 67 / 4 of 661; a lockfile regenerated with --config.allow-unused-patches=true -> pnpm install --frozen-lockfile exit 0 with no warning.",
"PM mechanism assumption 1 confirmed: the Locale surface step (after pnpm turbo run build, before Package the Worker) reads .next only.",
"Rules added beyond the two asked for, as NOT MEASURED guards: llms-page-body-missing (body set checked against the oracle) and no-link-targets (a consumer with 0 targets fails).",
"Empty branch pushed at cb0c146 as a write-route probe before the first real commit."
],
"open_questions": [],
"out_of_scope_findings": [
"filed as #284 (label finding, unassigned): the patches/fumadocs-core@16.8.12.patch header says a version bump drops the pin silently; pnpm 10.28.2 refuses the stale pin with ERR_PNPM_UNUSED_PATCH when the lockfile is regenerated, and is silent only after the pin is deleted or with unused patches allowed plus a frozen install"
]
}
Generated by Claude Code
hotlong commentedon Sep 24, 2026
ACCEPT: PR #285. Driving to landing. Notation:
AMPstands for one literal ampersand.Checklist, read from GitHub, not from the report.
main, and its first line isFixes #282.get_filesshows 2 files:.github/scripts/check-locale-surface.mjs(+578/−10) and.github/workflows/ci.yml(+11). Neither is on the governed surface.conclusionon head76dca92:buildsuccess,Node floorsuccess,Ownership & freshnesssuccess.Deploy docswasskipped, which is correct on apull_requestevent.Spot-checks, taken by the seat rather than read from the report.
ci.ymlchange comment-only?yaml.safe_load(branch) == yaml.safe_load(main)→ True. No step changed, and the size-gate block is untouched.107243512064log shows the live control line, then `--self-teston a fresh detached tree at76dca9228 case(s) over 14 rule(s). The blinded-scan cases both read red.NOT MEASURED — not built,artifact-missing×4The real regression, not only a fixture. The PR measures the defect on four builds, and on the #239 head as built: 67 references and 4 malformed targets, gate exit 1. That is the exact failure this gate exists for, shown on the PR that would have caused it.
d725081matches the card to the byte (695176).Deviations, all accepted:
check-locale-surface.mjs, not a new script. A new--self-testscript would have had to be registered intools/ci-scripts/run-self-tests.mjs, which is outside the file surface. This script also already owns the content oracle that makes "79 bodies expected" a checked claim.llms-page-body-missing,no-link-targets) keep either main rule from passing over nothing.ERR_PNPM_UNUSED_PATCH, exit 1). It turns silent after the pin is deleted, which is the first remedy pnpm's own error suggests, or when a lockfile is generated with unused patches allowed and then installed with--frozen-lockfile. My dispatch repeated the card's "silently" claim without measuring it. The dev corrected it. Filed as The fumadocs-core patch header says a version bump drops the pin silently; pnpm 10.28.2 refuses the stale pin when the lockfile is regenerated #284.Scope limits stated in the script header, accepted as written: the whole body is scanned, code fences included, and a titled link would read as malformed. Neither case occurs in today's corpus. Either one appearing later is a reason to change the rule on purpose, not to weaken it.
Path check before flipping ready: nothing governed. This lands, and⚠️ the merge publishes production. The change touches no build input, and the render gate was green on this head.
Generated by Claude Code
hotlong commentedon Sep 24, 2026
Landed and published. PR #285 was squash-merged as
c2d1181. All readings below come from the jobs' own step records on CI run 35945673804, not from the merge:build: success. TheLocale surfacestep, which now carries this gate, ran onmainand passed, followed byUpload the Worker bundle(success, not skipped) and the pre-merge render check (success).Deploy docs / Publish the tested Worker: success. Log line:✓ deploy: b3080544-4be8-40a8-9f20-845ca26d9103 is serving, replacing d67ec8e3-bddb-470b-809f-079f5a35cb90.Total Upload: 58546.02 KiB.Deploy docs / Smoke-check the live site: success. The rollback and failure-card jobs wereskippedbecause their conditions were false. As the seat post notes, a skipped rollback is not evidence that rollback works.pm:dispatchedwas removed by hand; aFixesauto-close does not remove it.What this changes for Dependabot #239. From now on, a
fumadocs-corebump that loses the patch failsbuildatLocale surfacewith 67 references and 4 malformed targets, and the log names the cause: the resolved version against the pinned version. #239's head was measured red on the PR. The real remedy is to regenerate the patch against the new version in the same PR. #284 carries the correction to the patch header.Generated by Claude Code