feat(challenge): delete the obligation whitelist, keep the seven facts that earn their place (closes #830) - #831
Conversation
…machine-only One `consider` call handed every answer roughly fifteen `must_state` facts, and an obligation list has one cheapest discharge: a sentence per item. The owner audit of that inventory asked, of each entry, what its must-have reason was, and found `basis.state_version` -- a content hash -- on the list of facts a human answer owes. `build_challenge` now returns three lists instead of one. `must_state` keeps what had a reason: which position this trade moves, which of the user's own rules it touches, which limitations change what a number refers to, and which day the book and the market were read at. `may_state` carries the two families that were recited every call and decided almost none -- the concentration readings and the cash line -- computed, addressed, and owed by default on no call. `machine_state` carries the hash, which is never rendered in any register. Nothing left the data layer. Every number is still computed, still anchorable, still citable; `rule_effects` and `required_coverage` are untouched, so the two silences that could help a user break their own rule stay machine-enforced rather than moving to judgment. The basis recital falls from five facts to two: the day the book was true and how old that makes it. `source` and `completeness` stay on the frozen row and still force a citation through `required_coverage`, which `basis.as_of`/`basis.stale_days` pay. Delivery gains a fail-closed half. `ux_receipt` refuses evidence whose presented answer renders a declared machine anchor -- refused rather than counted, because there is no acceptable number of content hashes to show someone mid-decision -- and stays silent on a block pasted from a build that predates the key. `check_expression` gains E-6, which fails a rendered machine anchor by shape on the answer text alone.
Section 2 said expression governs where an owed fact is said and never whether it is owed, and that was read as a disclaimer of volume. Nothing else claimed it: no rule anywhere governed how much an answer said, so every owed fact landed in body prose as its own sentence and a fifteen-item obligation list became a fifteen-sentence answer while D1-D6 stayed satisfied. D7 is the missing rule. A fact lives on exactly one floor -- deciding facts open the body, a truth-critical qualifier stays beside its number, sources and caliber and material gaps collect into one end block one line each, machine anchors are not rendered at all -- and a fact on two floors is a bug. It cannot license dropping an owed fact: a fact whose floor is the end block is still said, and the route's own obligation list is still the authority on which facts those are. SKILL.md's answer shape is rewritten to the reader's question chain rather than the payload's field order, and comes out 33 bytes shorter than before -- which the 16 KiB always-loaded budget required, and which is the right shape for a change whose subject is deletion. Two duplications went with it: the process-leakage sentence folds into D7's never-rendered floor, and the forecast-uncertainty rule stops being stated twice one paragraph apart. The candidate-persistence sentence is left verbatim; #829 owns it.
`references/trade-consequence.md` carried the obligation list and no argument for it, which is how a list nobody could defend grew to fifteen items. It now carries both tables: what was kept with the reason each keep is a keep, and what was deleted with what replaced it. The deletion is of obligations and never of data -- every number is still computed, anchored and citable, and the user can ask -- which is what makes not saying a number this decision does not turn on different from hiding it. "State" is redefined, because the old reading was the mechanism: stated = the fact appears with its correct anchor, and an inline number, a table cell, and a line in the end block all qualify. Fifteen obligations were never fifteen sentences; nothing had said so. The route synthesis gains the reader's question chain -- what you asked, the answer, why, what would overturn it, what to do -- in place of reading the payload's dependency order aloud, and the answer slots become opening body / falsifier / one end block. A third rendering joins the worked example: the same frozen payload under the new floor, three paragraphs where the whitelist-era one has three and the flat one has a wall, with the concentration sermon gone and the counter-case turned into the falsifier that replaced it. Witnesses, in three layers. The three owner-approved zh-TW acceptance templates join `expression-witnesses.json` as positive scenes. TA-001 gains `discharges_every_available_fact` (every reading the payload offers, stance in the last sentence -- eligible, complete, and failing only `decision_focus`, which is the point: no deterministic gate here can fail bloat), `deletion_first_compact` (the same call under the new floor, passing every production gate while never mentioning concentration or cash -- before this change it would have failed the delivery check for omitting those digits), and `renders_the_book_hash` (refused before any model call). The QA check-file example stops teaching the recital shape it was written under.
Implementer findings recorded for follow-up (none block this merge)
Owner picks which of these become issues. |
Post-merge live measurement (sanitized): deletion landed, length did not moveRe-ran the same four frozen scenes against merged main Composition: fixed. No basis recital, no rendered machine anchor, disclosures collapsed to one end block, the concentration family appears only when it carries the argument (present in the cash-deployment scene where it IS the thesis; fully silent in the no-book scene), answers open on stance + deciding reason, and the cash-deployment conversation's product-side total dropped ~49% (faster convergence, fewer rounds). Length: unchanged. First-answer character counts sit within ±5% of the pre-trim arm in all four scenes (~1.5k–2.7k zh chars vs the ~300-char acceptance templates, which pass as witnesses but do not attract the live skill). The obligation whitelist was worth only a few hundred chars per answer; the model refills the space with discretionary elaboration. This is direct evidence for the open owner call in this PR's follow-up comment (item 2): bloat has no deterministic gate, and prose norms alone do not converge length. If the owner wants the template-scale answers, the demoted reading-budget rule (first-screen self-sufficiency + delete-test per block + a deterministic oracle) is the remaining lever. Also: #829 reproduced exactly as scoped — the discovery scene again persisted its own unselected recommendation twice (-size and budget-size rows, both open, self-disclosed in-answer), while the selection scene stayed exact-1 (4 ephemeral probes all |
… layer, per-surface derivation additive-only (closes #832) (#833) * docs(expression): add the answer pyramid as the mother law of how this product speaks #830 deleted the obligation whitelist and its post-merge rerun (PR #831) moved first-answer length by less than 5% in all four frozen scenes. Deleting obligations vacates space; nothing positive said what an answer *is*, so the space refilled with discretionary elaboration. Meanwhile the answer-first principle existed five times, written five different ways, which is drift by construction. expression-contract.md gains section 3, the one statement of the shape every user-visible answer takes: one-sentence answer on top, an increment-gated middle (delete a block; if the decision does not change, delete it), the rest of the computed inventory behind a single offer, one caliber block at the end, and one paragraph of voice. Four named bans are encoded with the slugs the exemplar corpus references them by. Derivation by a surface is additive-only and an empty derivation is the default. V, D and C are frozen for shape and length: no new ID for "answers are too long" or "lead with X", because a sixth phrasing with an ID on it is still a sixth. V1 keeps its ID as the failure class its fixtures and cross-host rulings cite, marks its own definition superseded, and routes its shape half to the mother chapter. V10 stays unallocated. No character-count cap: #543's ceiling was deleted by #827 and stays deleted. Length is the shape's consequence, not its rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(surfaces): replace six local answer-first phrasings with one derivation each Every surface that stated the answer shape in its own words now derives it from the mother chapter and keeps only what it genuinely adds: - the review card is the document incarnation — keynote = top floor, the three middle blocks = the middle, the Block-1 footnote = the end block. Its structure does not change; the derivation is recorded so it stops reading as an independent statement of answer-first. - `consider` adds exactly two parameters: which fact wins the top sentence (lead selection) and which blocks the middle floor may hold (answer slots). Its reader's-question-chain section, the sixth phrasing the audit found, becomes that derivation. - no recorded book adds two: with no book the top sentence is a research-backed baseline, and the strategy-class map is a middle-floor block set. - the weekly market read adds one: its optional question comes after the complete brief. - freeform answers add nothing, and say so. An empty derivation is valid and is the expected case; text-first is a latency default, never a shape. - SKILL.md keeps the shape inline because it is always loaded, and is labelled as the mother chapter's projection rather than a second wording. It lands at 8,512 bytes, below its previous 8,522, and the always-loaded pair stays inside its budget. A funding shortfall now outranks every other lead candidate on `consider` (#778): a negative post-trade cash balance is not a portfolio consequence, it says the trade cannot be done out of the recorded book. The two numbers are support; the decision they imply is the answer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(expression): make the exemplars the spec, and wire the oracle to them The binding statement of the answer pyramid is the corpus, not the prose. `tests/agent/expression-witnesses.json` becomes schema 2: 14 canonical exemplars across the four conversational surfaces (three to five each, gated), each declaring the one-sentence answer it leads with and the increment every one of its blocks adds. The owner-approved acceptance templates from #830 are three of them; every issuer is fictional (WDGT, GRDC, FABR, ACME) and nothing is derived from a user record. Two new assertions, both honest about their half: - E-7 fails a scene whose declared core is not in its opening block. Its negative witness is a complete, anchored, obligation-discharging answer to the same call template 1 answers, which never says which candidate to buy. - E-8 fails a scene whose declared blocks are not all present in order, or which declares no increment for a block, or the same increment twice. Its negative witnesses are the manufactured all-in-one-name simulation (a block with no increment) and a closing summary that is the opening judgment in a second form. The two bans nothing mechanical reaches — a system default explained as insight, and a hedging couplet — get `counter` scenes that must PASS every assertion. The coverage boundary is asserted rather than promised: the day an oracle can catch one of them, that scene is what says so. #778's delivered answer joins as a second E-7 witness: the two cash numbers stated, the funding decision never, the opening spent on the boundary and hedging. `tests/test_expression_contract.py` gains the grep-checkable acceptance — zero retired answer-first phrasings remain, every surface declares a derivation, every named ban is defined in the mother chapter, the registry freeze is recorded in both the contract and the maintainer route, and no character-count cap came back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(no-book): pin what the retired phrasing protected, not the phrasing `test_research_priors.py` asserted the literal sentence "lead with the bounded value already supported", which is one of the six answer-first phrasings #832 replaces with a derivation. Restoring the sentence would defeat the change; dropping the assertion would lose a real protection (#597/#598: the user sees the bounded value before any intake question). So it pins the protection harder instead. The route's own block order must run baseline -> strategy-class map -> question, in that order — the question being last is now asserted rather than implied — and the section must declare itself a derivation of the shape rather than a second statement of it. The numeric question-cap regression still reddens. Recorded on the #832 mirrored-surfaces row, since a test that pins prose by literal is exactly the kind of reader a shape change has to carry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: test <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The flip
The product was too verbose because the engine handed every answer a whitelist:
roughly fifteen
must_stateobligations, anuncheckedlist, and pre-writtendisclosure sentences — while the expression layer explicitly disclaimed volume
("expression governs where an owed fact is said, never whether it is
owed"), and no rule anywhere claimed it. An obligation list has one cheapest
discharge: a sentence per item. The wall of tables the owner could not finish
reading was obligation discharge, not judgment.
Exhibit A from the owner's audit:
basis.state_version, a sha256 content hash,sat on the list of facts a human answer owes.
This flips the structure from whitelist obligations ("state these fifteen")
to blacklist prohibitions plus decision relevance ("never misstate or invent
— machine-gated, zero words; what gets said is chosen by what this decision
needs"). Every integrity gate is untouched, because they cost no reading.
Keeps and deletes
build_challengereturns three lists where it returned one.must_state— kept, with the reason each keep is a keep (now stated inreferences/trade-consequence.md):case_required)rule_effectsentryDeleted, and what replaced each:
basis.state_versionin the answermachine_state— never rendered, in any registeras_of,stale_days); staleness is said only when it could change the decisiontop3/ai_pct/max_sector_pct/ the triggers)may_state— surfaces when it is the deciding fact or touches the user's own capmay_state— when cash is the question or a floor is crosseduncheckedcounter_case: when_materialstays in the payload)disclosures_displayretargets thereThe deletion is of obligations, never of data. Every number is still
computed, anchored and citable, and the user can ask — which is what makes not
saying a number this decision does not turn on different from hiding it.
The two silences that could hurt stayed machine-enforced.
rule_effectsandrequired_coverageare byte-identical: a rule the user wrote is still namedand still refused-for-dropping. Only the engine's own default threshold became a
may-state.
sourceandcompletenessleavemust_statebut still force abasiscitation throughrequired_coverage, whichbasis.as_of/basis.stale_dayspay.The one grammar rule (D7)
docs/expression-contract.mdgains D7 — a fact lives on exactly one floor,and §2's wording changes so volume distribution is expression's business while
"whether a fact is owed" stays upstream.
A fact on two floors is a bug. Completeness lives in the data layer.
The reading-budget rule proposed as V10 in the issue is not adopted: a length
cap is what #827 had just deleted, and re-adding one prices the symptom instead
of removing the cause. It stays available as a backstop if deletion alone is not
enough.
Before / after (fictional issuers, per the tests/agent convention)
Same three-way comparison, same engine facts. Before, ~2,300 zh characters
opening on the concentration sermon both arms of the #827 A/B independently
wrote. After (
tests/agent/expression-witnesses.json,deletion_first_three_way_comparison, ~280):The stance opens it. The deciding reason is event risk, not a standing
concentration paragraph. The counter-side is one line, and the falsifier is
another. Book date, price session and the unchecked valuation gap are one end
block of one line each — never narrated twice. The two other approved templates
(recording a selection, and a no-book single-name decision) ship as witnesses
beside it.
The same shape on the repository's own worked English example is now the third
rendering in
trade-consequence.md's "One payload, three renderings".Fail-closed evidence
A green suite must not read as a governed output, so the witnesses cut both ways.
tools/ux_receipt.pyrefuses the delivery evidence when a declaredmachine_statevalue appears in the presented answer — refused rather thancounted, because there is no acceptable number of content hashes to show
someone mid-decision.
tests/agent/check_expression.pygains E-6, whichfails the answer text alone, by shape rather than vocabulary.
TA-001'srenders_the_book_hashproves it never reaches a model.test_the_users_own_rule_survives_the_deletion_with_its_gate_intactplus the existing
test_a_case_silent_about_a_collided_rule_is_refused_on_the_production_path:an answer recommending while dropping a touched user rule still fails.
TA-001'sdischarges_every_available_factstates everyreading the payload offers — the whole
may_statefamily, all five uncheckeddimensions, the basis — and reaches its stance in the last sentence. It is
deterministically eligible and factually complete, and it fails only
decision_focus. That is the point: no deterministic gate here can failbloat, which is why the witness exists.
deletion_first_compactis the same call under the newfloor: it never mentions concentration or cash and clears every production
gate. Before this change it would have failed the delivery check for omitting
those digits.
test_the_owed_floor_stayed_smaller_than_the_whole_inventoryfails if afamily quietly moves back onto the floor, or if the split becomes a deletion
of the data instead.
Compatibility
Additive and migration-free. The block is emitted, never stored, and never
enters
_evaluation_id's seed, so there is no payload at rest to migrate.may_state/machine_stateare new keys, required in the schema because theblock is computed fresh per call and an optional-but-always-present key is a
declaration nothing means.
must_state's topic enum narrows to matchTOPICSbecause
tests/test_evaluation_challenge.pyholds those two as an orderedequality.
ux_receiptstays silent on a challenge block that carries nomachine_statekey — absence is "nothing declared", never a clean result.skills/fomo-kernel/SKILL.mdcomes out 33 bytes shorter than before, whichthe 16 KiB always-loaded budget required and which is the right outcome for a
change whose subject is deletion. The candidate-persistence sentence is left
verbatim; #829 owns it.
Validation
Suite-level counts on the directly affected files:
test_evaluation_challenge41/41 (was 34),
test_consider207/207,test_expression_contract14/14 (was13),
test_interaction_trajectory153/153 (was 150),test_trade_answer_judge29/29,
check_expressionandcheck_voicewitnesses PASS.🤖 Generated with Claude Code