Every release, including the ones that correct our own numbers — honest math is the product, and this page is its paper trail. Rates are verified nightly against Anthropic's published pricing.
preview_upload and python3 -m acl_mcp.export --out records.jsonl. The YouTube embed, the play-button facade, and "Or watch the audit — 1:23, one take" are gone.preview_upload shows a sample of the records plus every field name that travels; python3 -m acl_mcp.export --out records.jsonl writes every scrubbed record. Same code, same salt, no server. /download serves agent_cost_lens_mcp-1.5.3.tar.gz. Earlier builds stay on 1.5.2..tar.gz. Quickstart is pip install "agent-cost-lens-mcp @ https://lens.r-lattice.com/download/agent_cost_lens_mcp-1.5.2.tar.gz", interpolated from TARBALL. Bare /download still serves the same bytes. HEAD works on both paths. uv refused the extensionless URL; pip did not."acl_YOUR_KEY". A // comment is not JSON; Claude Desktop refuses it.preview_upload no longer claims to print the exact payload. It shows a sample of the records and every field name that travels. python3 -m acl_mcp.export --out records.jsonl writes every scrubbed record. Same code, same salt, no server — that part was already true./dogfood.json as unknown_kind_days. Two orphan source-days from 22 and 24 August were blanking fifty real counts as *"not pushed"*.acl-server-staging is dead and unused (Ross, 2026-09-09). deploy_targets.json carries live only; --target staging is refused by name.#live-report) — the same fragment /dogfood wraps — so a reader does not leave the page to see it. An in-page link from the hero replaces the off-page /dogfood links. /dogfood itself stays as its own route (JSON twin, bookmarks, postflight).server/theme.py./dogfood and the landing hero used to print four rows reading *"not carried by the push"* — fresh input, 5-minute cache writes, 1-hour cache writes, and output — with the reason *"not on the wire — see below"*. That reason was false. The public MCP client has put all four on the wire per record since apilog-v1 (response.usage.input_tokens, cache_creation_input_tokens, cache_creation_1h_input_tokens, output_tokens); what dropped them was this server's own aggregation, which added the three input kinds into full_input_tokens and kept no output count at all. aggregate.rows_and_findings now sums each kind per (source, day) and storage keeps four new columns, so the rows read: fresh input, 5-minute cache writes, 1-hour cache writes, output — each with the stored column it came out of named beside it. The client did not change and no client version was cut; there was nothing on that side to ship.aggregates, nullable and default-less. fresh_input_tokens, cache_write_5m_tokens, cache_write_1h_tokens and output_tokens, appended in _SCHEMA and in _migrate in the same order, so a fresh table and a migrated one agree column-for-column. They take saved's shape rather than the component columns' NOT NULL DEFAULT 0, and the reason is sharper here: every row written before this release holds real traffic whose split nobody kept, so a default of 0 would publish a measured-looking zero for tokens that were certainly spent. Those rows read "not pushed" until the next push replaces them. The row's key is unchanged — UNIQUE (key_id, source, day), no model column: attributing counters to a model is a constraint migration of a different class and is its own job.saved's, and deliberately so: a token count renders as one number with no room for a caveat beside it, so a sum over some of the window's rows printed as the window's total would be short by exactly the rows it could not see./dogfood.json stays at surface dogfood-live/2. The four columns reach the wire by themselves — components.rows publishes the stored row dict — so they appear as four added keys with no edit to json_data. Every current reader reads by name, so none can misread an added key. The 1 → 2 bump was made for a removal, where a stale reader would read an absent key as an empty one; there is no such hazard in an addition.show_the_math.first_day_with_data, which answers *"the first day IN THIS WINDOW that carries data"*, so the value slid with the window while the noun stood still — and the coverage caveat beside it, fed by the same field, had it right all along. It now reads "The oldest day in this window with data is August 22". The ninth section's HEADING moved with its paragraph, from *"What is not on the wire"* to *"What the push carries, and what it does not"*: a heading naming a retired claim is the same defect as a paragraph making one, and it is what a reader meets first. All three are his words, on his ruling; his read of the deployed page is the approval."not carried by the push" is retired for "not pushed", and the note under the token table now says where the counts come from instead of explaining a wire limitation that never existed.GET /dogfood.pdf is deleted from server/app.py and now answers 404. The hero's *"the dogfood report"* link and the footer's *"the static dogfood report (PDF)"* link both point at /dogfood instead, so the page carries two pointers to one place where it used to carry two pointers to two documents. What that report served was generated by hand on 2026-08-22 and measured eleven sessions across two pools — a shape retired on 2026-08-25. It had been the first thing a reader met at the top of the page ever since./dogfood used to render a two-row table — one row titled *"Our own build (sessions and subagents)"* and a Combined total row beneath it carrying the identical figures, because there was one pool to combine. It now renders one set of numbers under one title, "Our own build", and nothing else. The parenthetical went with it: it was the retired subagent split still describing itself on the page, which is what he had read and objected to (*"I thought we took the subagent fleet out because that's just confusing. That's not out."*).dogfood_check still pins them against it; the second is no longer printed, because the split it describes is the thing that was ruled off the page./dogfood.json moves to surface dogfood-live/2. The pools array, the combined block and the per-label stamps map are retired; the document now carries one figures block and one components block at the top level, beside through, dormant, title, window_days and pricing_vintage. The version string is the point of the bump: a reader written against surface 1 does doc.get("combined") or {}, compares nothing, and cannot be told apart from a reader that agreed — so the contract names itself and a stale reader refuses by name instead. The local drift rail (dogfood_check.check_drift) reads the new shape and returns PENDING with both surface strings named when they disagree; it reads the expected value out of server/dogfood_page.SURFACE rather than keeping a second copy of it.cache_hit key so that a blended rate could not be rendered by accident. With one set of numbers there is nothing to blend: the rate is cache-read tokens over all input-side tokens across the window, token-weighted by construction. No figure moved — on the window measured at cut time the page showed a pool row of 99% beside a Combined row of "—", and now shows 99% once.llms.txt describes one report. It had said *"ONE pool since 2026-08-25 (sessions and subagents together); the shipped PDF report predates that and measured two"* — each clause true when written, each made false by the next ruling, with no test looking at it either time. It now describes the single report and says the PDF was retired, and a test grades it./dogfood and directly below the hero paragraph on the landing, so the saving stays the first figure a buyer meets and the explanation sits right under it. Two words are the page's rather than his, changed on his ruling: the copy says "this report's first push" because this server publishes one report now, and "a 30-day window ending on the newest day pushed" because that is the day the window really ends on — the same day the as-of sentence below it states. Every figure inside them is the page's own. Where a sentence names a number the page computes — the cache-hit rate, the saved percentage, the same percentage again as cents in the dollar, the window's length, the first day the report carries data — the value is interpolated from the render, never typed; the plan price is one constant that the plan sentence beside it also reads. The word "about" appears three times and only in front of a figure the page rounds, graded in both directions. The copy lives in server/plain_words.py, one owner rendering the same bytes on both surfaces. It renders in live mode only, and that is the honest half: every paragraph is a claim about the live report, so the block returns nothing at all when any figure it names is absent — the landing's static fallback and /dogfood's honest empty page carry none of it, rather than explaining figures that do not exist.<details> disclosure on the argument that three open copies would bury the sentences it supports; Ross ruled the other way, and an explanation a reader has to ask for is one most readers never meet.<details> to separate a CLAIM — a figure asserted in running copy — from the arithmetic that derives it: both page suites, and release_postflight's two-place fence, which requires the saving to appear twice (hero and report section) and would otherwise be counting the block's own workings as claims. An expression matching a deleted element matches nothing and keeps passing, so deleting the fold alone would have re-opened the defect 1.8.8 closed: measured on the real page, the saving appears 8 times whole and 2 times as a claim, and against a bound of two a whole-page count passes a fresh page (8), a half-stale one (7) and a wholly stale one (6) alike. The block now marks itself — show_the_math.BLOCK_OPEN — and every reader cuts on that marker. The two-direction proof is re-run on the real rendered page: fresh PASS · one stale claim FAIL · both stale FAIL.check_dogfood_live re-derives one report from components.rows and grades it against figures and the one rendered row, keyed off surface == "dogfood-live/2" and refusing by name otherwise; the Combined comparison and the pool loop go with the shape they graded. check_landing_figures was the second one, and it was found by measurement rather than named in the hand-up: it read combined and pools, found neither on the shipped wire, concluded the wire could not support a live hero, and graded the STATIC branch against a page serving LIVE figures — FAILing a correct deploy with "neither mode is served". Both now refuse a contract they do not read instead of reading the keys that happen to match.check_dogfood_pdf is deleted. It fetched /dogfood.pdf and compared it to the artifact on disk; the route is gone by the same ruling and answers 404, so the probe FAILED every correct deploy. Deleted rather than softened — a probe taught to accept a 404 would pass on a route that had merely broken. landing.DOGFOOD_PDF goes with it, and the archived report's filename moves to dogfood_check.ARCHIVED_REPORT, the one rail that still needs it: that check pins every DF_* constant against the archived document, and deleting the name to tidy the page would have disarmed it.as_of and through to be the same day — which with one report they are by construction — and refuses a wire that counts stored rows dated after its own as-of. Both fail on exactly the defect the old rule protected against, and both are graded in two directions.claude-fable-5-1's cache reads were priced at the generic ratio built for a model with no published rate of its own, and Fable 5.1 has one. The generic multiplier prices a cache read at 0.1x the input rate — $1.00/MTok on this model's $10 input rate. Anthropic's published rate for Fable 5.1's cache reads is $0.25/MTok, one-quarter of that. pricing.json now carries a cache_rates entry for models whose published cache rate is not the generic ratio, and price.cache_rates_for prefers it over the multiplier wherever one exists. The actual figure moved. On the pushed slice (2026-08-22 through 2026-09-05): actual $7,583.10 -> $7,004.87, share of the counterfactual 86% -> 87%. Without-caching and cache-hit are unmoved — the counterfactual charges every input-side token at the full fresh rate, which carries no cache rate at all, and the cache-hit ratio counts tokens, not dollars. Plainly: the earlier figure overpriced this one model's cache reads four to one. A model with no published row (claude-fable-5, still) is unaffected and still prices at the generic multipliers — that is the correct answer for it, not a workaround left uncorrected.<details>...</details> from the page before counting, so the bound counts claims, not bytes — measured against the real template in both directions: fresh PASS, one stale claim FAIL, both stale claims FAIL (the old behavior let all three states PASS).$0.00 by the conservative floor, up to $4.61 by the catalog's simulated ceiling — and the provenance paragraph beneath it. Both were honest; the first was in the wrong place, where an exact zero reads as "this product found nothing" a few lines from a $500/month price.PROVENANCE_STATIC, PROVENANCE_LIVE, their selector, the floor/ceiling template, DF_FLOOR, and hero_data's ceiling/floor keys. Restoring the generators turns 13 tests red, one of which fires even when the rendered page is byte-identical.DF_CEIL renders nowhere and stays — dogfood_check.DF_NAMES pins it against the cited report. A constant that verifies a published document is not a leftover, and deleting it as tidying would have retired a check./dogfood and in the report. The 1.6.1 never-alone rule was relocated, not repealed.LANDING_CEILING_OCCURRENCES was retired, not set to 0 — 0 makes if seen < 0 vacuously true. The probe now grades the pair as absent from the served bytes in both render modes.server/app.py said the static fallback carries PROVENANCE_STATIC. Deleting that constant made the sentence false; it is rewritten and pinned in both directions.DF_FLOOR, and one re-inserted the removed paragraph into the other's file before catching it. Everything was independently re-verified from disk afterward rather than taken from either report.**This release exists because a three-adversary audit of the client tarball
staged for the public repo found six claims that were false or misleading — two
of them in code that reaches customers, four of them in the release
documentation describing the release that fixed the last batch.** The audit's
other findings were clean: the install works, the mcp>=1.2,<2 pin holds at
both edges, the suite passes, the mutation tests are genuinely red, and the
denylist sweep found nothing. Every one of the six is stated here in full,
because a changelog that records only the clean half is an advertisement.
server/app.py health reports 1.8.6 and this heading carries the same
number. The MCP client is cut to 1.5.2. 55_acl_ops/uptime_watch stays
pinned to 1.8.5 until the last step of the deploy runbook (server/DEPLOY.md);
that bump is a deploy act, not a tree edit.
export.py told customers the file they named was the only thing it wrote. That was false, and it was false on three surfaces at once. The module docstring said "the file, and the file is the only thing written. There is still no store."; mcp-client/README.md said the same; and the --help text — which prints to a customer's terminal — said "Nothing is uploaded and nothing else is written." The export path calls scrub.load_or_create_salt(), which creates ~/.config/agent-cost-lens/salt (32 bytes, mode 0600) when none exists. Proven by running the shipped command under a throwaway HOME and diffing the tree: a first run creates two files, not one. A CPython audit hook over the same run enumerated every write anywhere on disk and found no others — and confirmed a second run with the salt already present opens it r and never rewrites it. The behavior is correct and is not changed. The salt is what makes a pseudonym STABLE: the same repo hashes to the same stand-in on the next run and to the same stand-in the upload path uses, which is the only reason an exported row and an uploaded row can be joined at all. Proven both directions — the same transcripts twice under one HOME produce byte-identical rows, and under a second clean HOME produce different tags. A salt held only in memory would be unjoinable and no more private. What was wrong was the sentence, not the write. All three surfaces now enumerate both files and state why the salt is a file, and the docstring and README additionally disclose the transient <out>.<rand>.part the atomic write creates beside the target — named in the module docstring before, never in the README.mcp-client/README.md stated that where the arithmetic supports a figure the catalog prices the fix, and where it does not the report says so rather than inventing one. core._pattern_lines did no such thing: it appended the dollar clause when a figure was present and silently omitted it otherwise, so a customer reading a pattern line with no money on it could not tell whether a figure had been withheld, was zero, or had never been computed. Rendered through the real advise.advise() on a finding that actually fires, the line read - volatile-prefix — 1 session affected and nothing more. The claim is now made true in code: an explicit null renders ", no dollar figure the arithmetic supports (none computed, not a recovery of zero)". It excludes zero without printing a zero, which is the distinction that matters — $0.00 would read as "we priced it and it is worth nothing", a different and false statement. A missing dollars key stays silent, deliberately: an explicit null is the server saying there is no figure, while an absent key is the server not speaking about money at all, and answering the second with a claim about the arithmetic would put a measurement in its mouth. The two cannot both arrive as None from .get, so the branch is guarded by an explicit sentinel.to_record can emit "12 distinct leaf paths in total — that is the entire apilog-v1 field universe, not a sample". The universe is 13. Swept over all 128 present/absent counter combinations through the real records.to_record and core._leaf_paths, the thirteenth path is response.usage itself: to_record drops zero-valued counters, so an all-zero record really does put "usage": {} on the wire, and _leaf_paths correctly treats an EMPTY container as a leaf in its own right (a non-empty one is walked into, and only its children are named). The repo's own suite already contained the counterexample — test_an_empty_container_is_named_rather_than_vanishing asserts exactly this — one test below the assertion the entry generalized from. Scope, which is what the original sentence actually got wrong. response.usage and its ten children are mutually exclusive; the container is a leaf only when it is empty, so no single record ever carries more than 12, and 13 is a union figure across records rather than a record's field count. The measured 12 was one payload's union — correct as a measurement, wrong as a ceiling. The paired "nine of the twelve unshown" figure is likewise correct only on a payload carrying no all-zero record; against the full 13-path universe the old line left ten unnamed. A bare response.usage.server_tool_use is not in the universe at all and must not be counted as a fourteenth — to_record sets that key only when the tools dict is non-empty. The same 12-as-universe framing is corrected in core._record_fields's docstring and in the test class that measures it, both of which additionally named a "6-record fixture" when the fixture in that test is 3 records.ba72adc (2026-07-20), which added payload["plan"] to push.build_payload, and became disclosed at 8b40aca (2026-08-22), which added core._plan_line and wired it into build_preview. git log --all -S returns exactly one commit for each marker string. Exact delta 33 days, 0:26:31. 22 matches no reading of the history — 22 days past 2026-07-20 is 2026-08-11, and there are no commits in that window. A dated CORRECTION block is appended to releases/v1.8.5.md; the original sentence is left standing rather than edited, because a silently corrected record is worth less than an honest one.advise.py appends exactly four findings; volatile-prefix (line 95) and nothing-to-fix (line 101) both carry "dollars": None. The same file's _COPY table agrees independently: those same two entries, and only those two, carry no {dollars} placeholder. Both have a real reason to carry no figure. volatile-prefix is detected purely structurally — simulate_cached is never called on that branch, and there is no counterfactual anywhere in the engine that prices pinning a prefix which changes on every request. nothing-to-fix fires on single-turn sessions, where caching has nothing to reuse, so the recovery is undefined rather than measured at zero. mcp-client/README.md carried the same one-pattern framing and is corrected with it.push.send would serialize is named in the preview text". The field line names every leaf path in the records — _record_fields is called on payload["events"], never on the payload — so three payload-level paths sit outside it by construction: schema, and plan.name / plan.monthly_cost when a plan is configured. Executed on a payload exercising every to_record branch: 16 distinct leaf paths with a plan, 13 named by the field line and 3 by the header and plan lines; 14 without a plan, 13 plus schema. Unnamed by all three: zero. So this is a precision defect, not a leak. It is worth correcting anyway, because the header and plan lines disclose their values by value — "Plan sent as billing context: Max 20x ($200.00/mo)" — and never as dotted paths, so "every leaf path is named" is true only under a reading of "named" the audit footer should not be trading on. The adjacent "Known limits" note is correct as written and is not touched: a brand-new top-level payload key really would travel unshown, because the field line cannot see that level. That gap is still open.core._pattern_lines, which adds a clause to a line it already rendered.releases/ keep their original wording. Corrections go forward and dated.**This release exists because 1.8.4 shipped three customer-facing defects and
nobody caught them until the SHIPPED artifact was audited rather than the
source.** All three are stated here in full, unsoftened, because a changelog
that only records wins is not a record.
server/app.py health reports 1.8.5 and this heading carries the same
number. The MCP client is cut to 1.5.1. 55_acl_ops/uptime_watch stays
pinned to 1.8.4 until the last step of the deploy runbook (server/DEPLOY.md);
that bump is a deploy act, not a tree edit.
mcp-client/pyproject.toml declared mcp>=1.0 with no upper bound. mcp 2.x renamed FastMCP, so from mcp.server.fastmcp import FastMCP — the first executable line of acl_mcp/server.py — raises ModuleNotFoundError there. A customer running pip install on a clean machine today resolves mcp 2.1.1 and gets a client that cannot start. The local suite passed the whole time because THIS machine happens to have mcp 1.28.1 installed: green for the wrong reason. A bound that only breaks on someone else's box is not caught by any test run on ours, and none of the three artifact fences — sdist content, source-vs-sdist drift, TARBALL pairing — looks at a dependency range.mcp>=1.0,<2 still admits 57 versions, and the oldest five — 1.0.0, 1.1.0, 1.1.1, 1.1.2, 1.1.3 — contain no mcp/server/fastmcp path at all; FastMCP first appears in 1.2.0. pip does not upgrade a dependency that already satisfies the range, so a customer with mcp 1.1.3 already installed sees Successfully installed agent-cost-lens-mcp, exit 0, and then the identical ModuleNotFoundError. The pin is now mcp>=1.2,<2, with the boundary verified by installing each side in a clean venv: 1.1.3 raises, 1.2.0 completes a full stdio handshake and lists both tools.preview_upload did not show what it promised to show, and its check could not have failed. The README and /llms.txt both say the preview output is the whole story. core._record_fields unioned the TOP-LEVEL keys of every record — a level records.to_record makes invariant, since it emits exactly {ts, source, session, response} every time. The union was therefore arithmetically incapable of ever differing from records[0].keys(): it was verification-shaped and proved nothing. Measured on this machine's 44,760 real records: 9 leaf paths travel and the line named 4 names, only 3 of which are fields — 6 paths traveled unshown, including every token counter, the model ID and the response ID. It now walks to the leaves and names the full path (response.usage.cache_read_input_tokens, not response), unioned across the whole payload rather than the sampled records.limit=0 the disclosure line vanished entirely — the one reader who asked to see no sample records was the one reader told nothing about what would be sent. limit controls the size of the SAMPLE and has never controlled the disclosure. The line is now rendered outside the limit guard, which is what this repo's own committed design at 20_product/src/acl/wire/preview.py:59-68 already did; the shipped client had regressed against a design published in the same checkout.acl_mcp/export.py carried the maintainer's real home-directory project folder name at lines 45, 144 and 171 as the --project example. Line 171 is argparse help text, so python3 -m acl_mcp.export --help printed it to any customer's terminal, and the other two are reachable through help() and by reading installed site-packages. agent_cost_lens_mcp-1.5.0.tar.gz, the file /download has been handing out, contains all three. The examples are now neutral (-home-you-code-myapp).tests/ in the sdist, so tests/test_export.py's two fixture constants were shipping the same string — and the second of them disclosed a SECOND drive name as well, not just the one already known. Both constants are now neutral (PROJ_A, PROJ_B); the leading-dash structure the fixture exists to exercise is preserved and all 18 export tests still pass.--help output was scanned as text and is clean. No salt value, key file or absolute salt path appears in anything shipped./llms.txt stopped describing a mechanism that no longer exists. Its "Audit it yourself" section still said the preview "enumerates the union of top-level keys across all records, so an added wire field cannot travel unshown" — a guarantee resting on the exact code that was replaced. It now describes the leaf-path union and says that the top-level version was 1.5.0's and could not widen. The same section's API block documented the request body as {schema, events} and omitted plan, which has traveled since 2026-08-03; plan is now named there.push.py, records.py). Repo-internal test and tooling files still carry others; that sweep is not in this release.schema, events and plan does neither — proven by adding one in a scratch copy. That is the exact mechanism by which plan itself traveled invisibly for 22 days. All three of today's top-level keys are disclosed, so the claim on the README and the landing page is true as shipped; the CLASS is not closed. The cure already exists in this checkout — the declared-renderer registry and parity_problems() in 20_product/src/acl/wire/preview.py, with a test that fails on a non-empty list. Porting it to the v1 client is not in this release.server/static/ still carry leaks and were not deleted. 1.4.0 and 1.5.0 carry the username; 0.1.0, 1.1.0 and 1.2.0 carry two estate vocabulary terms; 1.3.0 adds a governance token. None is reachable — server/app.py mounts no static handler and /download serves exactly landing.TARBALL — but they ride inside the deployed image, so any future static mount would expose all of them at once. Deleting published artifacts is a release decision, not a build-lane one.acl_mcp/parse.py:1 and acl_mcp/parse_apilog.py:5). They are public today. HEAD's tree is clean. Clearing the history means rewriting a public repository.COMMITTED 2026-08-25 on Ross's ruling ("I run it end to end", standing for this
release only). server/app.py health reports 1.8.4 and this heading carries
the same number. At the moment of this commit the live site still serves rev
00037-xj8 (v1.8.3), so zero defects below are fixed for a customer yet — the
deploy follows immediately and is recorded in its own DEPLOY commit, per this
repo's convention. 55_acl_ops/uptime_watch stays pinned to 1.8.3 until the
last step of the deploy runbook (server/DEPLOY.md); that bump is a deploy act,
not a tree edit.
The first block is Ross's ruling of 2026-08-25, after he reported the dogfood
rail's error as a SECOND incident and was then shown the rendered fleet row.
The blocks after it are the copy and gate work of the same day, which the
collapse made necessary.
80_acl2/30_serving/ re-homed both pool globs by path substitution. Main survived it; fleet did not — subagents are dispatched from job folders across TERRITORY3, never from the serving folder, so …-80-acl2-30-serving/*/subagents/*.jsonl matched zero files. The glob was not malformed, it was aimed at a shape the new home never had, and "UNMEASURABLE" graded identically to "one day behind on push": PENDING, exit 0.PoolEmpty was folded into the PENDING bucket on 2026-08-21 on one benign cause — transcript retention emptying a pool with no human act — against its own docstring, which says it must be loud. It then concealed the defect above. Retention and a misaimed glob are indistinguishable from inside the check, so the check no longer guesses: both are FAIL and the human says which it was. Exactly one rung — the live document being merely UNREACHABLE is transport, not blindness, and still exits PENDING.dogfood_math.METERED). Shown the rendered row — 1 day, $0.41 spend, 93% hit, a $0.00 ceiling and a $0.00 floor — Ross ruled it "junk i do not want in the dogfood report or on agent cost lens website", to stay out "till we figure out how to meter that effectively". Folder-scoped the pool reads zero; drive-scoped it reads ~5× main by attributing other TERRITORY3 projects' subagent spend to this product's build. Neither number is about this product. Declared unmetered, NOT deleted — labels, stored history and push path all stay, so re-metering is moving one string from UNMETERED to METERED. Both readers (the local rail and the served page) derive from that one tuple, so a pool cannot be live on one surface and retired on the other.no data pushed for this pool yet / no data in this window rows are gone — a placeholder row is still a row about a thing we are not reporting, and it is what made a retired pool look like a live measurement returning zeros. The pool is still named in the as-of sentence, so omitted-from-the-table never means omitted-from- the-record.dogfood_page.py:345 and :380 are still literal hardcoded strings, and the one-pool collapse made both FALSE rather than merely stale. See the CORRECTION block below.] Prose that counts pools is derived at call time, not frozen at import: "across both of our own pools", "on two keys that are ours" and the two-pool blend caveat each became false the moment a pool was retired. A page that miscounts its own sources is the exact failure this page exists not to commit.main() names the edge that failed. It previously printed the static pair's sentence, "the page disagrees with the report it cites", for ANY failure; once the drift edge could fail, that sentence became a confident false reason.dogfood_math.DORMANT, server/dogfood_page._derive). The as-of was min(both data-through stamps), so the fleet pool's 2026-08-22 stamp held the page at 2026-08-22 and put main's real 2026-08-24 row in rows_after_asof — visible on neither surface. It would have held there forever: a folder-scoped fleet glob cannot advance, because no subagent has ever been dispatched from the serving folder. The page cannot work this out for itself. In the store a dormant pool and a pool merely BEHIND on its push are byte-identical — same columns, same newest day, and aggregates carries no push timestamp; the one artifact that separates them is the push rig's per-pool last_push_utc, which lives on the client side of the wire and never crosses it. Any rule derived here would be a staleness cutoff in days wearing a rule's clothes, and it would start hiding a genuine partial push the moment one lagged past it. So the fact is DECLARED in one tuple both surfaces read, published on /dogfood.json, and stated in the as-of sentence. Un-declaring is deleting one label.release_postflight follows the same narrowing — it re-derives the as-of under the published declaration and FAILs a page that narrowed the rule without saying so. It deliberately does NOT certify dormancy itself: that is a fact about a source, and everything it is served is rows, so no re-derivation could confirm or deny it. Still missing: the local rail owns the glob and is the only thing on this machine that can see a dormant pool wake up. A declared-dormant pool whose glob starts matching files should FAIL there. Not built — that rung is Ross's.Recorded here rather than deleted, because the record of a reversal is worth more than a tidy
list. These three do NOT describe the tree:
dogfood_math.py:57 meters both pools and the fleet row renders", which stopped being true when he collapsed the split. dogfood_math.METERED is (POOL_LABEL,) — ONE pool, both transcript shapes — and there is no fleet row to render. The reversal is still recorded because it happened; the sentence describing the tree is corrected because it no longer did.dogfood_page.py renders both placeholder rows (NO_ROWS_IN_WINDOW, NEVER_PUSHED), and the KEEP decision is written into the code beside the constants so the next reader does not re-litigate it.dogfood_page.py:345 and :380 are still literal hardcoded strings". They are not, as of the copy work later the same day — pools_phrase, blend_line and ours_line all derive at call time now, and _headline/ours_line derive from the pools that CONTRIBUTED rather than the pools that were declared. The REVERTED marker stays because Ross's reversal is a fact about that sitting; what the tree does today is described in the copy block below.What survives from that work is the mechanism, not the ruling: METERED/UNMETERED remain as the
switch, now joined by DORMANT for the separate question of which pool may pin the as-of.
ThePoolGlobs. Recorded rather than quietly dropped, because the widened figure is what exposed how far off the fleet number was.dogfood-report-2026-08-22 is a published historical artifact and was NOT edited — it is evidence of what was measured then. Removing the fleet section from what customers read means regenerating the report, which is a release act and Ross's hand.DF_HIT_FLEET stays a constant so the static-pair check still verifies the cited report, which does carry that figure; it is asserted absent from the rendered page.check_drift with a non-existent glob on a METERED pool → FAIL, exit 1, naming the pool and the pattern.TwoPools re-meters it in tests so the N-pool machinery (per-source ceilings, per-pool floors, older-of-two as-of stamp, never-blended cache-hit) is still covered for the day it returns.server/ 273 · mcp-client/ 39 + 3 skipped.Three rulings, verbatim:
> "you can switch off that cried wolf on a good known state. That's fine."
>
> "I think we just get rid of the fleet pool. I mean, it's just... I think multiagent
> and agent spend should just be one pool. Like, all operations should be agent cost
> lens. It shouldn't be sub split. Maybe later I can add that capability, but now
> it's fine."
>
> "the wake up check, a declared dormant pool whose glob starts matching files and
> said fail. make that, write it."
These supersede several entries above. dogfood_math.METERED is now
(POOL_LABEL,), UNMETERED and DORMANT are both (), and MAIN_LABEL/
FLEET_LABEL are one constant, POOL_LABEL. The stored label STRING is unchanged
(dogfood-main): it is the label of a key Ross minted by hand and of every row
already pushed under it, so renaming the value would orphan the history and need a
new key. What changed is the split, not the identity.
#### Changed
dogfood_check.POOL_PATTERNS is (<project>/*.jsonl, <project>/*/subagents/*.jsonl) — still FOLDER-SCOPED, because "keep it folder scoped" was not reversed. local_rows sweeps a SEQUENCE of patterns and is empty only when every one of them matched nothing; a test proves no two patterns can match the same file, which is what makes sweeping them separately safe.claude-code, day 2026-08-24), and the subagent shape alone still matches 0. So the pool is non-empty and the rung goes quiet on its own. Live run after the change: PASS / PASS / PENDING, exit 0 — where it was FAIL, exit 1.asof_sentence is written over N stamps instead of a named pair; the blend caveat no longer says "the two pools".hero_data returns hit instead of hit_main/hit_fleet and returns None for anything but exactly one pool — the hero sentence has room for a single rate and blending pools' rates is forbidden everywhere, so a re-split falls back WHOLE to the static copy rather than inventing a number. release_postflight.check_landing_figures follows the same arity, or it would grade the static mode while the page served live figures.DF_HIT_FLEET stays, and so does combine(). Both describe the SHIPPED REPORT, which measured two pools on the day it was generated and will say so forever. check_report_figures pins DF_HIT_FLEET against that document; dropping it would delete a figure a rail verifies in order to tidy a page that is still true. The static fallback renders both constants byte-identically to the pre-collapse page.#### Added
dogfood_check.check_dormant_wakeup, FAIL, exit 1) — the rung the entry above called "Still missing". A pool declared dormant whose glob has started matching files is a FAIL naming the pool and the first path found. It lives beside the globs and not in dogfood_math, because dormancy is declared on the served side and disproved on the LOCAL filesystem, and a served module carrying a local-filesystem fact is a rule living apart from where it bites. It is proven by tests that supply their own dormant pool, because production now declares none — running the shipped declaration would prove only that an empty loop does nothing. A dormant pool this rail owns no glob for is also FAIL, not PASS: a check that could not look must never report success._source_note). _day_divergences compares spend and saved, both per-day SUMS that cannot move when a pool's events are repartitioned across sources; the ceiling, the floor and the source count are counted once per source and are NOT compared. That gap becomes reachable with the merged pool, so when the counts differ the rail says so instead of reporting PASS on the figures it never looked at. PENDING — nothing a deploy does causes it or fixes it.server/app.py guards hero_data(store) in a try but calls landing_html OUTSIDE it, so a hero dict missing a key the template interpolates would raise inside the response. HERO_KEYS is now pinned from both sides and a hero missing any key falls back WHOLE — proven for every key, not only the one that moved.#### The fake source, decided rather than left open
A subagent transcript's project is the literal folder name subagents
(parse.py:69), and the WIRE's source is derived from the project
(records.py:41,52). So once a subagent is dispatched from the serving folder the
STORE will hold two sources in the one pool, one of which is not a project.
Accepted, not repaired. The collision it invites needs two projects' subagents in
one pool, and every pattern of this pool lives under ONE project directory — the
folder scope is the guard, and a test asserts it mechanically. The money is unharmed:
simulate_cached and recoverable_conservative both clamp PER SESSION and
aggregate runs them once per source, so a second source partitions the pool's
events rather than duplicating them. The name never reaches a surface. The repair
would be in parse.parse_file, which mcp-client/tests/test_parity.py pins
byte-identical to the SHIPPED client the push rig loads — a client release, and
Ross's call. Pinned in both directions so the trap is written down, not rediscovered.
#### Consequences, stated rather than smoothed over
push_dogfood.py execs dogfood_check.py out of the working tree at run time — no deploy in between — and pairs POOL_GLOBS with (MAIN_GLOB, FLEET_GLOB). Both names are KEPT here so the rig reaches its own arity check and raises its own Refusal (exit 2, caught, legible) instead of dying on an uncaught AttributeError. Simulated against the real rig today: *"dogfood_check names 1 pools but this rig knows 2 globs — a new pool is a deliberate change to this rig (it needs its own key), not something to infer."* That refusal is CORRECT and cannot be avoided from this repo: a pool's label is a property of the API KEY on the server side, so the merged pool needs the subagent shape pushed under the MAIN key, and only the rig can do that. Ross's edit, in 55_acl_ops/, which this seat does not touch. Delete MAIN_GLOB/ FLEET_GLOB the same sitting the rig is patched, and not before.dogfood-fleet rows already in the store are orphaned, not deleted: the page selects by label and no longer asks for that one. Its 21 events / 1 day stay in aggregates under a key that still exists. Deleting them is a prod-DB act.dogfood-report-2026-08-22 was NOT edited — it is evidence of what was measured then, and the static-pair check still passes against it, all 7 constants.combine_windows still combines a list. Kept, because the wire shape is what release_postflight re-derives from and because the split may come back. Whether the page should still show it is copy, and copy is Ross's.#### Two-direction probe (§1.7 requirement)
PoolEmpty, which is what made the old fleet pool FAIL every run.server/ 294 · mcp-client/ 39 + 3 skipped.#### Fixed
check_drift compared spend and saved on the days BOTH sides hold. Both are per-day SUMS, and the clip discards every day only one side holds — so rows landing on days only the STORE holds (a re-pushed archive, another machine's key, a rig aimed at the wrong glob) moved the source-level figures while every compared cell still agreed, and the rail printed "clipped overlap agrees to the cent". Measured: the unfiltered archive push staged the same day would have taken the pool 89× and the ceiling 249× (00_inbox/2026-08-25_dogfood-recovery/STAGED_PUSH.md) and passed this edge clean. _window_notes now re-derives BOTH sides over the full served window — the as-of and window length taken off the wire, not assumed — and compares the two figures the clip structurally cannot see: source count and ceiling. It runs BEFORE the overlap gate, because "no shared day at all" was the one state most likely to be an errant push and the one state nothing looked at. Loud, not blocking: every finding is PENDING, and the direction is stated because the resolutions are opposite (store above local = investigate; local above store = push). An empty local window says UNMEASURABLE rather than comparing against $0.00. _source_note is subsumed, not duplicated, and the PASS sentence now names the full window instead of claiming only the overlap.run_analyze read advice.recoverable out of the server's dict and dropped advice.patterns, so the product's own differentiator — the landing page's "your specific cache-waste, named, with what fixing it recovers" — never reached the customer who paid for it. They got a dollar figure with no name and no fix. Now rendered: pattern name, sessions affected, recoverable labeled (simulated), and the advice line quoted VERBATIM from the server so the client cannot drift from what the engine says. Absent is not empty — a server naming no catalog gets silence, an explicitly empty one gets "no cache-waste pattern fired", and junk off the wire is skipped rather than rendered or raised on. parse.py untouched (tests/test_parity.py pins it byte-identical to the shipped client).client_rail.py refused the release while the source carried the fix and mcp-client/pyproject.toml still read 1.4.0 — one version never names two artifacts — and passed once bumped: server/static/agent_cost_lens_mcp-1.5.0.tar.gz (29 members) installed, landing.TARBALL moved to it. Verified by extracting the BUILT tarball and executing its _pattern_lines, not by reading the source tree. NOTE the preflight's client sdist == landing TARBALL fence compares the NAME and checks the file exists; it cannot see stale contents, so it stayed green through the whole stale period. Rail 1 is the only check that catches this, which is why it is first in DEPLOY.md's order./v1/analyze omitted breakdowns the landing page promises. server/landing.py:183 has told every visitor since 1.0 that "back comes spend by model/day/source" and analyze.analyze computed all three the whole time; the response discarded them. Added as by_model / by_day / by_source, capped by BREAKDOWN_CAPS (50 / 400 / 100). Days and models are bounded by reality; sources are whatever the customer names them and a 55,000-event push can carry thousands, so the cap keeps the response growing with spend rather than with naming habits. It keeps the BIGGEST entries, not the first ones the dict hashed into, and truncated names what was dropped — count and dollars — because a breakdown that no longer sums to the headline is a quiet lie unless the gap is named. Honest-empty: truncated is {} when nothing was cut. by_source reads by_repo and the two ARE the same map on this route (parse_apilog._event sets project and source to the one source field the wire carries); the wire's word is source, so that is the response's word.price.cost_of returns $0.00 for a model it has no rate for — deliberate, so one unknown model cannot zero a run — analyze collected the names, and the response dropped them. A customer running a model the table does not know got an understated total that looked like a working answer. Now surfaced on both halves: unpriced in the response, and a segment in lens.summary_line naming up to three models and saying the figures above UNDERSTATE the real spend. Absent key and empty list both mean silence.#### Changed
analyze.recoverable_conservative: cache-write premium paid that read savings never paid back, clamped per session. ONE waste pattern, silent about every other — and contradicted one clause earlier by the larger ceiling over the same window. It now says which waste it counts and that it is a lower bound on a single pattern, not a verdict.MAIN_GLOB/FLEET_GLOB were kept alive a few hours as a compatibility surface for the push rig, which execs dogfood_check.py from the working tree on a timer with no deploy step between. The rig now resolves its pools from POOL_GLOBS + PATTERNS_BY_LABEL and reads neither name, so both are deleted; TheOpsRigInterlock inverted from pinning their presence to pinning their absence, so the retired split cannot return by import.#### Not done, and why
landing_html, llms_txt and dogfood_html and searching the output, not by grep alone: no live surface states a session count. The claim exists only in server/static/dogfood-report-2026-07-{22,24,31}.html, which are published reports and evidence — never edited. Those files ARE reachable at /dogfood.pdf, so the claim is still customer-visible; the resolution is report regeneration (T2b), which is a release act and Ross's hand.#### Two-direction probes (§1.7 requirement)
/dogfood.json → PENDING, naming both window ceilings to the cent ($1,155.94 served vs $4.61 local), naming the hidden source, and no longer claiming "agrees to the cent". Allowed: local == served → PASS, and silent about ceilings and sources.claude-zeta-9 → the response names it in unpriced and the summary says the total UNDERSTATES; proven with teeth by showing the unpriced event moved the total by exactly $0.00. Allowed: a fully priced run → unpriced == [] and the summary says nothing.00_inbox/2026-08-25_fix-all/PROBES_serving-lane.txt, red run in RED_RUN_serving-lane.txt.#### Not deployed
server/app.py was 1.8.3 when this block was written; it is 1.8.4 as of the copy-and-gate block below.00_inbox/2026-08-25_fix-all/preview/. It corrects the brief's premise — the $4.61 → $0.00 move does NOT happen when the as-of unpins. The two pushes came from two different project folders (…Large-50-agent-cost-lens and …TERRITORY3-80-acl2-30-serving), which are two different source pseudonyms, so at as-of 2026-08-24 both sources are members and the ceiling stays $4.61 (spend $67.52). The ceiling falls to $0.00 (spend $0.13) on 2026-09-22, when the 08-22 row rolls out of the trailing 30 days — and the pool is folder-scoped to the new home, so nothing will ever refresh that row.server/ 300 · mcp-client/ 45 + 3 skipped.The collapse retired a pool. It did not retire the SENTENCES about that pool, and
a twelve-lane audit found them by RENDERING every surface rather than grepping
source — which is also how it found that "57 main working sessions", the string
everyone suspected, renders nowhere, while two nobody suspected were live and
false. Six fix lanes ran, six adversaries attacked their work, and a serialized
integration pass resolved what the adversaries proved. Every entry below is
tree-only.
#### Fixed — the landing page (the surface that sells at $500/month)
/dogfood has carried the attribution sentence since it shipped; the page that SELLS rendered neither "platform caching" nor "credit" while printing the rate. An unowned rate on a sales page is a claim by omission. ATTRIBUTION now rides in the same <p> as the figure (asserted absent from <footer> — a disclaimer under the fold is a hedge), with ATTRIBUTION_SHORT above the fold beside the savings rate.DF_HIT_FLEET was NOT deleted: dogfood_check.check_report_figures pins it against the published PDF, and deleting a verified figure to tidy a page is how a rail gets quietly disarmed.dogfood_math.money rounds each figure on its own. Solved in PRESENTATION — ROUNDING_NOTE states the cause beside the numbers, in both paragraphs that carry all three. It is the one arithmetic check a skeptical buyer runs by hand on a page whose pitch is "audit us before you trust us".#### Fixed — the landing page, second pass (what the adversary refuted)
Four of the fixes above shipped a new false sentence with them. All four were
proven by rendering or by EXECUTING the product, never by reading source.
llms_txt in the same file already said 2026-08-25 — one product, two answers to one date. Both now interpolate COLLAPSE_DATE.advise.patterns returns [] and report.py emits no fix section at all — that sentence is emitted by no code path in this tree. The only string the product owns is advise._COPY["nothing-to-fix"], gated on SINGLE-TURN sessions. The page now quotes that string verbatim (derived from advise, like _VOLATILE_QUOTE) and describes the real behavior: no pattern fires, the report names none. The catalog bullet claiming already-good caching is "reported as exactly that" was corrected on the same grounds. The same misquote shipped in mcp-client/README.md and is corrected there.saved — a counterfactual (analyze.already_saved) — into <span class="num"> put an unlabeled counterfactual first, with "(simulated)" in the next sentence attached to a different number. landing.py's own module docstring makes the label binding and report.py was corrected against that same line today. "(simulated)" is now inside the emphasis, graded per-emphasis rather than once per page.#### Fixed — /dogfood
contributed() is now the ONE predicate the table row and the prose both read._PROSE held a bare noun, every sentence prefixed a literal "the", and the collapse's possessive value rendered "the our own build pool's data" on the live hero path. Determiner and noun are one string now. The regex guarding against it was case-sensitive and is now case-insensitive — a sentence-initial "The our..." walked past the test written to catch it._COLUMNS tuple; the span is read at call time, so a test that adds a column watches the span follow. _cells' docstring said "six" while emitting seven — no count is written in prose there any more, and the width is asserted against _COLUMNS where it can be checked.day_rows 1, gap_days 29. /dogfood disclosed those gaps; the hero it feeds did not, so the caveat sat one click from the figure it qualified. hero_window now carries "on the 1 day with data in the measured window (…)". It adds no key (a key landing.HERO_KEYS does not know is a KeyError on the page that sells) and refusing would have needed an unmeasured coverage threshold — a bound that speaks only when it breaks.#### Fixed — the API and the release gates
/v1/health reports 1.8.4, and this heading is what makes release_preflight's version fence agree. releases/v1.8.4.md accompanies it._capped's docstring contradicted the wire. It said truncated was "absent from the response when nothing was cut"; the wire has ALWAYS sent the key, honest-empty ({}). A release gate read the docstring and wrongly blamed the CHANGELOG. A test now bans the word "absent" from that docstring — prose cannot be checked for meaning, but it can be kept from making the one claim the wire contradicts.landing_html was called OUTSIDE the route's try/except, so only the FETCH was guarded, not the render. Both are inside now, with a static re-render on any fault. Probed 11 ways: each of the 8 HERO_KEYS dropped in turn, plus window="" and window=5 — which landing.py's is None fallback let through into the f-string. A build whose STATIC render also raises still returns 500: loud, not swallowed.LANDING_CEILING_OCCURRENCES graded a shape the page stopped having. The gate counted the recoverable ceiling twice on the served page — "hero + report section", the Buddah copy gate of 2026-07-31 — and the hero reorder moved the ceiling out of the hero. Verified by RUNNING check_landing_figures against the real rendered page: FAIL in both the static and the live branch. The decoy was setting it to 1 and stopping — that satisfies the probe by deleting the fence, leaving a check that proves only "the ceiling appears somewhere". The rule is intact and rides the figure that is still in both places: the ceiling is expected once, and LANDING_SAVED_OCCURRENCES = 2 carries the two-place fence.test_release_postflight.py is a hand-written landing page — correct for grading the probe, but it meant nothing in the tree had ever run the gate against the real template. Exactly one test now serves landing.landing_html itself and grades only the thing neither side can check alone: that the shape the probe counts is the shape the template renders.DF_ACTUAL/DF_UNCACHED/DF_SAVED/DF_PCT are one name per line. They were a tuple assignment, unreadable to release_postflight.landing_constant, whose whole point is reading this file as TEXT rather than importing the product's dependencies. Teaching the probe to parse tuples would be solving a formatting choice with a parser.#### Fixed — tests that could not fail
A mutation battery run against the fix lanes' own tests found five assertions
that stayed green while the behavior they claimed to pin was destroyed. Every one
graded CHARACTERS where the defect is MEANING.
ROUNDING_NOTE rewritten to claim the figures "always reconcile exactly" — the precise opposite of what it discloses, refutable with a calculator. Denials are now banned from the constant.ATTRIBUTION rewritten into word salad that CLAIMED the credit while keeping "does not claim credit for it" buried inside as a substring. The disclaimer is graded as the constant's own final SENTENCE, whole.ATTRIBUTION_SHORT moved out of the hero and under the buy button — the test defined "the hero" as everything before the "How it works" heading. It is pinned to the hero PARAGRAPH now.<span style="display:none"> — visible() stripped tags but did not honor the CSS that suppresses them, so an invisible sentence read as present. It now removes inline display:none and hidden elements with their content.All five turn red now; a no-op control mutation stays green.
#### Two-direction probes (§1.7 requirement)
advise._COPY string can produce. Allowed: the volatile-prefix and nothing-to-fix quotes, both derived from the engine.#### Not done, and why
server/static/dogfood-report-2026-07-{22,24,31}.html and -2026-08-22.html read "blending the two pools without token weighting". They are evidence and were NOT edited — and /dogfood.pdf resolves to one of them, so the retired claim stays customer-visible until the reports are regenerated, which is a release act and Ross's hand.dogfood_math.money still formats each figure with its own ,.2f, so any surface printing a part and its total can still show a cent's difference.55_acl_ops/uptime_watch stays pinned at 1.8.3 — the last step of the deploy runbook, not a tree edit. Flagged so nobody "fixes" it early.#### Not deployed
server/ 371 · mcp-client/ 45 + 3 skipped. Baseline at the start of this block was 437 / 300 / 45+3sk.prose_span lives in dogfood_math (the date-arithmetic owner; dogfood_page's no-clock rail bans the datetime marker), months hardcoded so the process locale can never translate the marketing copy. The /dogfood page keeps ISO dates.DF_WINDOW now derived from the new DF_WINDOW_SHORT so the page prose and the card date cannot drift; card rebuilt, manifest pins the new constant./dogfood renders — through ONE dict, so the two surfaces cannot drift apart. When the wire cannot support every quoted figure (empty store, partial saving, a silent pool) the page serves the blessed static 08-22 copy, byte-identical to the pre-live page; a store fault serves the same rather than a 500. DESIGN Q6's static-hero rule becomes the fallback: its quiet-day fear is answered by the window's width. The provenance paragraph travels with the figures — the static "fixed at the release" claim would be false over live figures.80_acl2/30_serving/; Ross's clock, dashboard task 69): first image built and deployed from the new home; Large's copy is tombstoned at ab47c07. Version bumped so the T3-built image ships as :1.8.1 instead of moving the registry's Large-built :1.8.0 digest — the tag the live image-identity probe compares against (Ross's word, 2026-08-25 sitting).--target staging|live on deploy.sh, with NO default (Ross's ruling 2026-08-22, the G3 finding). deploy_targets.json is now keyed by target and every invocation names where it goes; a targetless one refuses before resolving anything. staging is acl-server-staging in the same region and registry, min_instances: 0 — and its own image name, so a staging push can never move the version tag the live image-identity probe compares against. Config only: the first staging deploy is Ross's hand.git that cannot answer is a FAIL: an unprovable claim is not a clean tree.release.sh — the weekly one-touch's preflight half. Chains client_rail.py --run → release_preflight.py --with-tests → deploy.sh plan --target staging, then STOPS and prints the Ross-steps still outstanding (count derived from the list, never restated) beside the v1.8.0 baseline of 1 tagged / 2 nominal. It cannot reach a deploy: the only deploy.sh invocation it carries is the staging plan, and that is pinned by test.release_postflight.py --target staging --base-url <url> — the six service probes and nothing else. It HARD-REFUSES --bump-uptime-pin and --records before any probe fires (both are live-release bookkeeping; the pin is what pages Ross), and --base-url is refused for --target live, whose URL comes from the ops rig rather than a command line.llms.txt describe the product as it is now (Ross's word 2026-08-22). The headline names both halves — the waste and the fix — and adds the one the releases since 1.7.0 earned: you can read exactly what we would see. "How it works" states the 272-byte record (403 before), the 12.1× gzip, the salted message-id pseudonyms, and nightly rate verification; the audit section carries python3 -m acl_mcp.export --out records.jsonl, pinned to the client's own argparse prog string so the page cannot advertise an invocation the client will not answer to. The /dogfood link's cadence pre-claim is corrected: B2's landed line promised "live figures, updated daily" and said the push "runs unattended", both ahead of a push timer that is not ruled live — it now reads "live figures" and advertises the honest empty state instead. A test bans cadence words in that paragraph while leaving "nightly" sayable where it is true. Three new ground-truth pins tie the Quickstart's console script, env var names, and tool names to mcp-client/ itself; all three passed against the unmodified block, which is the verification that it was already correct. Copy and tests only — no API, client, or server behavior change; positioning constants and hero DF_* figures untouched, so the og-card is unchanged.mcp-client/pyproject.toml → 1.4.0; sdist built at server/static/agent_cost_lens_mcp-1.4.0.tar.gz, 29 members, carrying export.py, records.py, and the split push.py that the served 1.3.1 tarball predates). The preview now renders the plan field (plan name + monthly cost, carried in payloads since 08-03 but never shown) and enumerates the union of top-level keys across all records — an added wire field can no longer travel unshown. Both landed red-first. Served client remains 1.3.1 until the next deploy.client_rail.py's no-drift check now walks the SOURCE set, both directions. The old fingerprint compared sdists only, so a file ADDED to mcp-client/ after the served tarball was built appeared in no comparison — the bare run printed "nothing to do" over real drift (the vacuous check recorded at v1.8.0 ship time). It now reads the packaging declaration and reports ADDED / MISSING / CHANGED; a seeded extra source file refuses loudly, and the real three-file drift (export.py, records.py, push.py) was the first thing it caught.pricing.json carried Sonnet 5 as a $3/$15 base with a $2/$10 override through 2026-08-31, so on September 1 the service would have silently started pricing every Sonnet 5 turn at a rate that no longer exists: Anthropic's pricing page now states the $2/$10 introductory price "is now the standard price" and the scheduled increase "will not occur" (verified 2026-08-21). The standing price is now the base rate with no override left to expire, and the regression test pins all three date paths ($2/$10 before, after, and with no date) plus the empty override list, so an expiring override cannot return unnoticed.python3 -m acl_mcp.export --out FILE sweeps the same transcripts preview_upload shows you, scrubs them with the same salt, and writes one apilog-v1 record per line (JSON Lines) — so a program on your own machine can read exactly what the analyzer would have received, with no server in the loop. --since / --until bound the window; --project narrows the sweep to a single ~/.claude/projects/<folder>, and because every such folder name begins with -, the command takes those names as values rather than mistaking them for flags. Three properties are enforced rather than promised: the export path imports nothing from the upload path and nothing that can open a socket (asserted by both an import-graph probe and a source scan, each proven in both directions); each record is identical to what the upload would have sent for the same event and salt, so exported and uploaded rows join on the same pseudonyms; and the file is replaced atomically — a crash leaves the previous file or no file, never half of one. There is still no background store: nothing is written unless you run this command.client_rail.py takes an mcp-client/ change all the way to a servable sdist (vendor → the client's tests → build → server/static/ → landing.TARBALL) and refuses when the source changed without a version bump — one version never names two artefacts. deploy.sh carries the load-bearing --memory/ --concurrency flags, read from server/config.json rather than restated, so a forgotten flag can no longer re-open the OOM class; no sub-command implies another, and deploy runs the preflight first. release_postflight.py probes the live service six ways, each expected value read from a real source, and prints the estate records the release still owes. Internal tooling only — no API, client or server behaviour change.to_record, the scrub applied to every record, and the schema name now live in records.py, which imports nothing that can reach a network; push.build_payload and the new export both call it, so the two can never drift into producing different records. push.to_record and push.scrub_record still resolve, and the wire payload is unchanged — verified byte-for-byte against the previous implementation over the 2,014-event replay fixture, with and without a plan.mcp-client/pyproject.toml still reads 1.3.1 on purpose, because the repo's steady state is that the version in pyproject always names a sdist already in server/static/ (test_client_rail.py::RealRepoState). The client source has changed underneath 1.3.1, so client_rail.py will REFUSE and ask for the bump at ship time — which is the rail working, not a fault. Nothing was built, installed, served or deployed by this entry.server/storage.py::upsert_findings now records the pruner's standing constraint in its docstring (WO-4 gate §7.1): the prune is safe only while every push is a full push.llms.txt now describe what actually shipped. The page still read as early July: advice was said to appear only on API-log runs (it rides the default transcript path since 1.4.0), and nothing mentioned that message ids are pseudonymized (1.4.0), that uploads travel gzipped when both ends agree (1.5.0), or that a request now carries up to 55,000 events (1.6.0). Copy only — no API or client change.preview_upload audit./v1/health which body encodings the server accepts and gzips the upload only if gzip is on that list. A server that does not offer it receives exactly the plain body it receives today, so clients and servers can be upgraded in either order and in either combination. Measured on 9,978 real records: 2.72 MB → 225 KB (12.1×). This is still apilog-v1 — the document is unchanged, only the number of bytes it travels in.Content-Encoding the server never advertised is refused by name. Previously an unrecognised encoding would have been read as plain text and reported as malformed JSON, which describes the wrong problem.response.id was travelling to the analyzer verbatim — the only field on the wire that was not already a local HMAC pseudonym. It is now tagged like source and session are. Nothing reads it downstream (it serves only as a dedupe key), so no capability changed; the raw Anthropic id simply stops leaving your machine.T00:00:00Z the reader immediately discards, and the server-tool counters were spelled out as zeros on every single record. Both are now omitted. Measured on 9,548 real events: 403 → 272 bytes each. This stays apilog-v1 — the reader already tolerated both, now pinned by a contract test, so an older server reads the new records unchanged and clients and servers can upgrade in either order.too_large and too_many_events said only what the limit was, which reads as a broken server rather than "ask for less history". They now say to narrow the window with --since / --until.advise() was gated on --apilog, so a plain lens.py run over local transcripts — the way most runs happen — never showed the pattern catalog or the simulated recovery, while the hosted analyzer showed both for the same data. It reads no apilog-only field, and its simulator's ground-truth band was measured against real Claude Code transcripts (re-measured this release at 0.797, inside [0.70, 1.30]), so the transcript path is the validated case. A run the simulator cannot improve keeps its previous wording exactly.max_body_bytes. The compaction above roughly triples the history that fits (~12,400 → ~18,350 records) but does not remove the ceiling; when you hit it, narrow the date window. max_events remains unreachable — the body cap binds first.og:image + Twitter card served at /og-card.png, rendered at build time from the same constants as the hero (server/build_og_card.py); a manifest (constants + sha256) is test-pinned so a stale card fails the suite instead of shipping./robots.txt and /sitemap.xml routes.mint_key.py now accepts --expires-days N to set a key's expiry window at creation time.psycopg.OperationalError; sqlite/test paths are byte-identical (defaults catch nothing).server/requirements.txt pinned to the serving image's exact versions — rebuilds can no longer silently change the pooler-facing client library.pip install "agent-cost-lens-mcp @ https://lens.r-lattice.com/download" — verified against the live endpoint. No version number on the page, so it can never go stale; /download always serves the current build. (Also fixes the old two-step: curl -LO saved the file under the wrong name.)~/.config/agent-cost-lens/plan.json: {name, monthly_cost}) and every figure is worded for subscription reality: dollars become *API-equivalent value*, savings and recoverable become *usage-limit headroom*, and the summary adds an equivalent-run-rate multiple of the plan price.plan field on the apilog-v1 wire envelope — strictly validated (bad_plan 400 on malformed), absent for every existing client./v1/health now reports the real release version (was a stale phase tag).Root 119 · client 19 (incl. byte-parity, now a vendored quintet) · server 58.
First public version — everything currently serving at lens.r-lattice.com.
Retroactively tagged; see releases/v1.0.0.md for the full report.
--push.advise.py): ground-truth-gated (sim/CC ratio locked by test); every estimate labeled *simulated* / *up to*.analyze_costs + preview_upload tools; never raises past itself.llms.txt, /download tarball, lens.r-lattice.com domain.